From adc9ab9c52602d5550e2cdb526c7a717fa2811af Mon Sep 17 00:00:00 2001
From: Walter Rafelsberger <walter.rafelsberger@elastic.co>
Date: Thu, 28 Sep 2023 11:49:13 +0200
Subject: [PATCH 01/20] [ML] AIOps: Enable
 `event_generating_elements_should_be_instrumented` eslint rule. (#167317)

Implements #153108.

This enables the
`@kbn/telemetry/event_generating_elements_should_be_instrumented` eslint
rule for the `aiops` plugin to enforce `data-test-subj` attributes on
actionable EUI components so they are auto-instrumented by telemetry.

The ids were first auto-created using `node scripts/eslint --fix
x-pack/plugins/aiops` and then adapted.
---
 .eslintrc.js                                               | 1 +
 .../components/change_point_detection/fields_config.tsx    | 3 +++
 .../change_point_detection/max_series_control.tsx          | 1 +
 .../log_categorization/category_table/table_header.tsx     | 2 ++
 .../log_categorization/loading_categorization.tsx          | 7 ++++++-
 .../log_categorization/log_categorization_page.tsx         | 7 ++++++-
 .../sampling_menu/random_sampler_range_slider.tsx          | 1 +
 .../log_categorization/sampling_menu/sampling_menu.tsx     | 1 +
 .../log_rate_analysis/log_rate_analysis_results.tsx        | 6 +++++-
 9 files changed, 26 insertions(+), 3 deletions(-)

diff --git a/.eslintrc.js b/.eslintrc.js
index 6657ba3cb1f01..4e46336ec70ae 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -908,6 +908,7 @@ module.exports = {
     },
     {
       files: [
+        'x-pack/plugins/aiops/**/*.{js,mjs,ts,tsx}',
         'x-pack/plugins/apm/**/*.{js,mjs,ts,tsx}',
         'x-pack/plugins/exploratory_view/**/*.{js,mjs,ts,tsx}',
         'x-pack/plugins/infra/**/*.{js,mjs,ts,tsx}',
diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/fields_config.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/fields_config.tsx
index 45b2ccc1c097a..c07af22a5f16a 100644
--- a/x-pack/plugins/aiops/public/components/change_point_detection/fields_config.tsx
+++ b/x-pack/plugins/aiops/public/components/change_point_detection/fields_config.tsx
@@ -356,6 +356,7 @@ const FieldPanel: FC<FieldPanelProps> = ({
               <EuiSpacer size={'m'} />
 
               <EuiButton
+                data-test-subj="aiopsChangePointDetectionSubmitDashboardAttachButton"
                 fill
                 type={'submit'}
                 fullWidth
@@ -441,6 +442,7 @@ const FieldPanel: FC<FieldPanelProps> = ({
           <EuiFlexGroup alignItems={'center'} gutterSize={'s'}>
             <EuiFlexItem grow={false}>
               <EuiButtonIcon
+                data-test-subj="aiopsChangePointDetectionExpandConfigButton"
                 iconType={isExpanded ? 'arrowDown' : 'arrowRight'}
                 onClick={setIsExpanded.bind(null, (prevState) => !prevState)}
                 aria-label={i18n.translate('xpack.aiops.changePointDetection.expandConfigLabel', {
@@ -480,6 +482,7 @@ const FieldPanel: FC<FieldPanelProps> = ({
                 id={`panelContextMenu_${panelIndex}`}
                 button={
                   <EuiButtonIcon
+                    data-test-subj="aiopsChangePointDetectionContextMenuButton"
                     aria-label={i18n.translate(
                       'xpack.aiops.changePointDetection.configActionsLabel',
                       {
diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/max_series_control.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/max_series_control.tsx
index b7f98ca82f90e..4f2929751006a 100644
--- a/x-pack/plugins/aiops/public/components/change_point_detection/max_series_control.tsx
+++ b/x-pack/plugins/aiops/public/components/change_point_detection/max_series_control.tsx
@@ -42,6 +42,7 @@ export const MaxSeriesControl: FC<{
       label={inline ? undefined : label}
     >
       <EuiFieldNumber
+        data-test-subj="aiopsMaxSeriesControlFieldNumber"
         disabled={disabled}
         prepend={inline ? label : undefined}
         append={
diff --git a/x-pack/plugins/aiops/public/components/log_categorization/category_table/table_header.tsx b/x-pack/plugins/aiops/public/components/log_categorization/category_table/table_header.tsx
index 42e382c0a4c0b..5635936f80302 100644
--- a/x-pack/plugins/aiops/public/components/log_categorization/category_table/table_header.tsx
+++ b/x-pack/plugins/aiops/public/components/log_categorization/category_table/table_header.tsx
@@ -51,6 +51,7 @@ export const TableHeader: FC<Props> = ({
           <>
             <EuiFlexItem grow={false}>
               <EuiButtonEmpty
+                data-test-subj="aiopsLogPatternAnalysisOpenInDiscoverIncludeButton"
                 size="s"
                 onClick={() => openInDiscover(QUERY_MODE.INCLUDE)}
                 iconType="plusInCircle"
@@ -61,6 +62,7 @@ export const TableHeader: FC<Props> = ({
             </EuiFlexItem>
             <EuiFlexItem grow={false}>
               <EuiButtonEmpty
+                data-test-subj="aiopsLogPatternAnalysisOpenInDiscoverExcludeButton"
                 size="s"
                 onClick={() => openInDiscover(QUERY_MODE.EXCLUDE)}
                 iconType="minusInCircle"
diff --git a/x-pack/plugins/aiops/public/components/log_categorization/loading_categorization.tsx b/x-pack/plugins/aiops/public/components/log_categorization/loading_categorization.tsx
index 77ba11fd46cc5..208302083fd0c 100644
--- a/x-pack/plugins/aiops/public/components/log_categorization/loading_categorization.tsx
+++ b/x-pack/plugins/aiops/public/components/log_categorization/loading_categorization.tsx
@@ -43,7 +43,12 @@ export const LoadingCategorization: FC<Props> = ({ onClose }) => (
           <EuiFlexItem>
             <EuiFlexGroup justifyContent="spaceAround">
               <EuiFlexItem grow={false} css={{ textAlign: 'center' }}>
-                <EuiButton onClick={() => onClose()}>Cancel</EuiButton>
+                <EuiButton
+                  data-test-subj="aiopsLoadingCategorizationCancelButton"
+                  onClick={() => onClose()}
+                >
+                  Cancel
+                </EuiButton>
               </EuiFlexItem>
             </EuiFlexGroup>
           </EuiFlexItem>
diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx
index 5ccdca64d1036..bfa609bb5dd21 100644
--- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx
+++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx
@@ -324,7 +324,12 @@ export const LogCategorizationPage: FC = () => {
               />
             </EuiButton>
           ) : (
-            <EuiButton onClick={() => cancelRequest()}>Cancel</EuiButton>
+            <EuiButton
+              data-test-subj="aiopsLogCategorizationPageCancelButton"
+              onClick={() => cancelRequest()}
+            >
+              Cancel
+            </EuiButton>
           )}
         </EuiFlexItem>
         <EuiFlexItem />
diff --git a/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/random_sampler_range_slider.tsx b/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/random_sampler_range_slider.tsx
index 4f2b62729ca46..2cd70b35d139e 100644
--- a/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/random_sampler_range_slider.tsx
+++ b/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/random_sampler_range_slider.tsx
@@ -92,6 +92,7 @@ export const RandomSamplerRangeSlider = ({
           data-test-subj="dvRandomSamplerProbabilityRange"
           append={
             <EuiButton
+              data-test-subj="aiopsRandomSamplerRangeSliderApplyButton"
               disabled={isInvalidSamplingProbabilityInput}
               onClick={() => {
                 if (setSamplingProbability && isDefined(samplingProbabilityInput)) {
diff --git a/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/sampling_menu.tsx b/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/sampling_menu.tsx
index e4d18f6dbc260..c4e16d4fabbe6 100644
--- a/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/sampling_menu.tsx
+++ b/x-pack/plugins/aiops/public/components/log_categorization/sampling_menu/sampling_menu.tsx
@@ -118,6 +118,7 @@ export const SamplingMenu: FC<Props> = ({ randomSampler, reload }) => {
       id="aiopsSamplingOptions"
       button={
         <EuiButtonEmpty
+          data-test-subj="aiopsLogPatternAnalysisShowSamplingOptionsButton"
           onClick={() => setShowSamplingOptionsPopover(!showSamplingOptionsPopover)}
           iconSide="right"
           iconType="arrowDown"
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx
index f1b1a6d38de12..a62db054d4d52 100644
--- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx
+++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx
@@ -409,7 +409,11 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({
               )}
               {overrides !== undefined ? (
                 <p>
-                  <EuiButton size="s" onClick={() => startHandler(true)}>
+                  <EuiButton
+                    data-test-subj="aiopsLogRateAnalysisResultsTryToContinueAnalysisButton"
+                    size="s"
+                    onClick={() => startHandler(true)}
+                  >
                     <FormattedMessage
                       id="xpack.aiops.logRateAnalysis.page.tryToContinueAnalysisButtonText"
                       defaultMessage="Try to continue analysis"

From fdf0ab763b0069a5174971b39b0ed3f90cbc5092 Mon Sep 17 00:00:00 2001
From: Stavros Kroustouris <kroustou@users.noreply.github.com>
Date: Thu, 28 Sep 2023 11:54:36 +0200
Subject: [PATCH 02/20] Change GPCTL dashboard link (#167466)

---
 .github/workflows/create-deploy-tag.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/create-deploy-tag.yml b/.github/workflows/create-deploy-tag.yml
index abe2e131165ec..85e226d384cc2 100644
--- a/.github/workflows/create-deploy-tag.yml
+++ b/.github/workflows/create-deploy-tag.yml
@@ -102,7 +102,7 @@ jobs:
               "<https://github.com/elastic/kibana/actions/runs/${{ github.run_id }}|GitHub Workflow run>",
               "<https://buildkite.com/elastic/kibana-serverless-release/builds?branch=${{ env.TAG_NAME }}|Kibana Serverless Release pipeline>",
               "<https://argo-workflows.us-central1.gcp.qa.cld.elstc.co/workflows?label=hash%3D${{ env.COMMIT }}|Argo Workflow> (use Elastic Cloud Staging VPN)",
-              "<https://platform-logging.kb.us-central1.gcp.foundit.no/app/dashboards#/view/f710d7d0-00b9-11ee-93d2-8df4bca5a4c9?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-1d,to:now))&service-name=kibana&_a=(controlGroupInput:(chainingSystem:HIERARCHICAL,controlStyle:oneLine,ignoreParentSettings:(ignoreFilters:!f,ignoreQuery:!f,ignoreTimerange:!f,ignoreValidations:!f),panels:('18201b8e-3aae-4459-947d-21e007b6a3a5':(explicitInput:(dataViewId:'logs-*',enhancements:(),fieldName:commit-hash,id:'18201b8e-3aae-4459-947d-21e007b6a3a5',selectedOptions:!('${{ env.COMMIT }}'),title:commit-hash),grow:!t,order:1,type:optionsListControl,width:medium),'41060e65-ce4c-414e-b8cf-492ccb19245f':(explicitInput:(dataViewId:'logs-*',enhancements:(),fieldName:service-name,id:'41060e65-ce4c-414e-b8cf-492ccb19245f',selectedOptions:!(kibana),title:service-name),grow:!t,order:0,type:optionsListControl,width:medium),ed96828e-efe9-43ad-be3f-0e04218f79af:(explicitInput:(dataViewId:'logs-*',enhancements:(),fieldName:to-env,id:ed96828e-efe9-43ad-be3f-0e04218f79af,selectedOptions:!(qa),title:to-env),grow:!t,order:2,type:optionsListControl,width:medium))))|GPCTL Deployment Status dashboard>",
+              "<https://overview.qa.cld.elstc.co/app/dashboards#/view/serverless-tooling-gpctl-deployment-status?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-1d,to:now))&service-name=kibana&_a=(controlGroupInput:(chainingSystem:HIERARCHICAL,controlStyle:oneLine,ignoreParentSettings:(ignoreFilters:!f,ignoreQuery:!f,ignoreTimerange:!f,ignoreValidations:!f),panels:('18201b8e-3aae-4459-947d-21e007b6a3a5':(explicitInput:(dataViewId:'serverless.logs-*',enhancements:(),fieldName:commit-hash,id:'18201b8e-3aae-4459-947d-21e007b6a3a5',selectedOptions:!('${{ env.COMMIT }}'),title:commit-hash),grow:!t,order:1,type:optionsListControl,width:medium),'41060e65-ce4c-414e-b8cf-492ccb19245f':(explicitInput:(dataViewId:'serverless.logs-*',enhancements:(),fieldName:service-name,id:'41060e65-ce4c-414e-b8cf-492ccb19245f',selectedOptions:!(kibana),title:service-name),grow:!t,order:0,type:optionsListControl,width:medium),ed96828e-efe9-43ad-be3f-0e04218f79af:(explicitInput:(dataViewId:'serverless.logs-*',enhancements:(),fieldName:to-env,id:ed96828e-efe9-43ad-be3f-0e04218f79af,selectedOptions:!(qa),title:to-env),grow:!t,order:2,type:optionsListControl,width:medium))))|GPCTL Deployment Status dashboard>",
               "<https://buildkite.com/elastic/kibana-tests/builds?branch=main|Quality Gate pipeline>"
             ]
       - name: Post Slack failure message

From c48cc24617df8ebcf78b1d4847bdcbca11e974e8 Mon Sep 17 00:00:00 2001
From: Dzmitry Lemechko <dzmitry.lemechko@elastic.co>
Date: Thu, 28 Sep 2023 12:06:00 +0200
Subject: [PATCH 03/20] [kbn/journeys] fixes to run journeys against ESS
 cluster (#166923)

## Summary

I had to change `waitForRender` since `page.waitForFunction` tries to
run a script on page and it is not working due to CSP settings on Cloud.
Instead of injecting a script, we use a classical API to find
elements/attributes in the DOM.

Since `PUT /internal/core/_settings` is merged in 8.11.0, journeys run
on Cloud with on-fly labels update is supported starting deployments
8.11.0+. I added error message for 404 code just in case someone runs it
on earlier version.

`many_fields_discover` journey was update since on Cloud the data view
used by scenario is not selected by default.

How it works:

Create a deployment with QAF and re-configure it for journey run:
```
export EC_DEPLOYMENT_NAME=my-run-8.11
qaf elastic-cloud deployments create --stack-version 8.11.0-SNAPSHOT --environment staging --region gcp-us-central1
qaf elastic-cloud deployments configure-for-performance-journeys
```

Run any journey, e.g. many_fields_discover
```
TEST_CLOUD=1 TEST_ES_URL=https://username:pswd@es_url:443 TEST_KIBANA_URL=https://username:pswd@kibana-ur_url node scripts/functional_test_runner --config x-pack/performance/journeys/many_fields_discover.ts
```

You should see a log about labels being updated:

```
Updating telemetry & APM labels: {"testJobId":"local-a3272047-6724-44d1-9a61-5c79781b06a1","testBuildId":"local-d8edbace-f441-4ba9-ac83-5909be3acf2a","journeyName":"many_fields_discover","ftrConfig":"x-pack/performance/journeys/many_fields_discover.ts"}
```

And then able to find APM logs for the journey in
[Ops](https://kibana-ops-e2e-perf.kb.us-central1.gcp.cloud.es.io:9243/app/apm/services?comparisonEnabled=true&environment=ENVIRONMENT_ALL&kuery=labels.testJobId%20%3A%20%22local-d79a878c-cc7a-423b-b884-c9b6b1a8d781%22&latencyAggregationType=avg&offset=1d&rangeFrom=now-24h%2Fh&rangeTo=now&serviceGroup=&transactionType=request)
cluster
---
 .../running_performance_journey_in_cloud.mdx  | 29 +++++++++--
 packages/kbn-journeys/journey/journey.ts      | 15 ++----
 .../journey/journey_ftr_config.ts             |  4 +-
 .../journey/journey_ftr_harness.ts            | 36 +++++++------
 packages/kbn-journeys/services/auth.ts        | 14 ++----
 packages/kbn-journeys/services/es.ts          | 20 ++++++++
 .../services/ftr_context_provider.ts          | 13 +++++
 packages/kbn-journeys/services/index.ts       | 28 +++++++++++
 packages/kbn-journeys/services/page/index.ts  |  5 +-
 .../kbn-journeys/services/page/kibana_page.ts | 50 +++++++++++--------
 .../journeys/many_fields_discover.ts          |  6 ++-
 11 files changed, 159 insertions(+), 61 deletions(-)
 create mode 100644 packages/kbn-journeys/services/es.ts
 create mode 100644 packages/kbn-journeys/services/ftr_context_provider.ts
 create mode 100644 packages/kbn-journeys/services/index.ts

diff --git a/dev_docs/tutorials/performance/running_performance_journey_in_cloud.mdx b/dev_docs/tutorials/performance/running_performance_journey_in_cloud.mdx
index 3f8b373afad39..6ab160ac93328 100644
--- a/dev_docs/tutorials/performance/running_performance_journey_in_cloud.mdx
+++ b/dev_docs/tutorials/performance/running_performance_journey_in_cloud.mdx
@@ -11,7 +11,7 @@ tags: ['kibana', 'onboarding', 'setup', 'performance', 'development', 'telemetry
 As a way to better understand user experience with Kibana in cloud, we support running performance journeys against
 Cloud deployments.
 The process takes a few steps:
-- Create a cloud deployment
+- Create a cloud deployment (8.11.0+ is supported)
 - Re-configure deployment with APM enabled and reporting metrics to the monitoring cluster
 - Create a user with `superuser` role to run tests with
 - Checkout the branch that matches your cloud deployment version
@@ -35,7 +35,7 @@ Navigate to `Advanced Edit` page and change `Deployment Configuration` by adding
 ```
 "user_settings_override_json": {
   "tracing.apm.enabled": "true",
-  "tracing.apm.environment": "development",
+  "tracing.apm.agent.environment": "development",
   "tracing.apm.agent.service_name": "elasticsearch",
   "tracing.apm.agent.server_url": "<SERVER_URL>",
   "tracing.apm.agent.metrics_interval": "120s",
@@ -50,6 +50,7 @@ Navigate to `Advanced Edit` page and change `Deployment Configuration` by adding
 
 ```
 "user_settings_override_json": {
+  "coreApp.allowDynamicConfigOverrides": true,
   "elastic.apm.active": true,
   "elastic.apm.breakdownMetrics": false,
   "elastic.apm.captureBody": "all",
@@ -74,8 +75,28 @@ Note: DEPLOYMENT_ID and YOUR_JOURNEY_NAME values are optional labels to find the
 
 Save changes and make sure cluster is restarted successfully.
 
+### Use QAF to prepare the deployment
+The quickest way to prepare ESS deployment is to use [QAF](https://github.com/elastic/qaf):
+
+- Make sure to add `~/.elastic/cloud.json` and ~/.elastic/cloud-admin.json with Cloud API (to create deployment) & Cloud Admin API (to modify it) keys
+```
+{
+  "api_key": {
+    "production": "<PROD KEY>",
+    "staging": "<STAGING KEY>",
+    "qa": "<QA KEY>"
+  }
+}
+```
+- Create deployment and modify it
+```
+export EC_DEPLOYMENT_NAME=kibana-perf-8.11
+qaf elastic-cloud deployments create --stack-version 8.11.0-SNAPSHOT --environment staging --region gcp-us-central1
+qaf elastic-cloud deployments configure-for-performance-journeys
+```
+
 ### Run the journey
-Make sure you have created user with `superuser` role and the Kibana repo branch is matching your deployment version.
+Make sure the Kibana repo branch is matching your deployment version.
 Set env variables to run FTR against your cloud deployment:
 
 ```
@@ -90,4 +111,6 @@ Run your journey with the command:
 node scripts/functional_test_runner.js --config x-pack/performance/journeys/$YOUR_JOURNEY_NAME.ts`
 ```
 
+APM & Telemetry labels will be updated on the fly and metrics/traces should be available in Telemetry Staging and kibana-ops-e2e-perf cluster.
+
 
diff --git a/packages/kbn-journeys/journey/journey.ts b/packages/kbn-journeys/journey/journey.ts
index 7952b8ee7408d..bf3de796265f9 100644
--- a/packages/kbn-journeys/journey/journey.ts
+++ b/packages/kbn-journeys/journey/journey.ts
@@ -12,14 +12,9 @@ import { Page } from 'playwright';
 import callsites from 'callsites';
 import { ToolingLog } from '@kbn/tooling-log';
 import { FtrConfigProvider } from '@kbn/test';
-import {
-  FtrProviderContext,
-  KibanaServer,
-  Es,
-  RetryService,
-} from '@kbn/ftr-common-functional-services';
-
-import { Auth } from '../services/auth';
+import { FtrProviderContext } from '../services/ftr_context_provider';
+import { Es, KibanaServer, Retry, Auth } from '../services';
+
 import { InputDelays } from '../services/input_delays';
 import { KibanaUrl } from '../services/kibana_url';
 
@@ -37,7 +32,7 @@ export interface BaseStepCtx {
   kbnUrl: KibanaUrl;
   kibanaServer: KibanaServer;
   es: Es;
-  retry: RetryService;
+  retry: Retry;
   auth: Auth;
 }
 
@@ -141,7 +136,7 @@ export class Journey<CtxExt extends object> {
       getService('kibanaServer'),
       getService('es'),
       getService('retry'),
-      new Auth(getService('config'), getService('log'), getService('kibanaServer')),
+      getService('auth'),
       this.config
     ).initMochaSuite(this.#steps);
   }
diff --git a/packages/kbn-journeys/journey/journey_ftr_config.ts b/packages/kbn-journeys/journey/journey_ftr_config.ts
index 1abc141c7bbae..b60f6fa191978 100644
--- a/packages/kbn-journeys/journey/journey_ftr_config.ts
+++ b/packages/kbn-journeys/journey/journey_ftr_config.ts
@@ -11,7 +11,7 @@ import Path from 'path';
 import { v4 as uuidV4 } from 'uuid';
 import { REPO_ROOT } from '@kbn/repo-info';
 import type { FtrConfigProviderContext, FtrConfigProvider } from '@kbn/test';
-import { commonFunctionalServices } from '@kbn/ftr-common-functional-services';
+import { services } from '../services';
 
 import { AnyStep } from './journey';
 import { JourneyConfig } from './journey_config';
@@ -66,7 +66,7 @@ export function makeFtrConfigProvider(
         bail: true,
       },
 
-      services: commonFunctionalServices,
+      services,
       pageObjects: {},
 
       servicesRequiredForTestAnalysis: ['performance', 'journeyConfig'],
diff --git a/packages/kbn-journeys/journey/journey_ftr_harness.ts b/packages/kbn-journeys/journey/journey_ftr_harness.ts
index 9df84821de032..0f649e7a1de27 100644
--- a/packages/kbn-journeys/journey/journey_ftr_harness.ts
+++ b/packages/kbn-journeys/journey/journey_ftr_harness.ts
@@ -9,20 +9,19 @@
 import Url from 'url';
 import { inspect, format } from 'util';
 import { setTimeout } from 'timers/promises';
-
 import * as Rx from 'rxjs';
 import apmNode from 'elastic-apm-node';
 import playwright, { ChromiumBrowser, Page, BrowserContext, CDPSession, Request } from 'playwright';
 import { asyncMap, asyncForEach } from '@kbn/std';
 import { ToolingLog } from '@kbn/tooling-log';
 import { Config } from '@kbn/test';
-import { EsArchiver, KibanaServer, Es, RetryService } from '@kbn/ftr-common-functional-services';
 import {
   ELASTIC_HTTP_VERSION_HEADER,
   X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
 } from '@kbn/core-http-common';
 
-import { Auth } from '../services/auth';
+import { AxiosError } from 'axios';
+import { Auth, Es, EsArchiver, KibanaServer, Retry } from '../services';
 import { getInputDelays } from '../services/input_delays';
 import { KibanaUrl } from '../services/kibana_url';
 
@@ -40,7 +39,7 @@ export class JourneyFtrHarness {
     private readonly esArchiver: EsArchiver,
     private readonly kibanaServer: KibanaServer,
     private readonly es: Es,
-    private readonly retry: RetryService,
+    private readonly retry: Retry,
     private readonly auth: Auth,
     private readonly journeyConfig: JourneyConfig<any>
   ) {
@@ -63,15 +62,24 @@ export class JourneyFtrHarness {
   private async updateTelemetryAndAPMLabels(labels: { [k: string]: string }) {
     this.log.info(`Updating telemetry & APM labels: ${JSON.stringify(labels)}`);
 
-    await this.kibanaServer.request({
-      path: '/internal/core/_settings',
-      method: 'PUT',
-      headers: {
-        [ELASTIC_HTTP_VERSION_HEADER]: '1',
-        [X_ELASTIC_INTERNAL_ORIGIN_REQUEST]: 'ftr',
-      },
-      body: { telemetry: { labels } },
-    });
+    try {
+      await this.kibanaServer.request({
+        path: '/internal/core/_settings',
+        method: 'PUT',
+        headers: {
+          [ELASTIC_HTTP_VERSION_HEADER]: '1',
+          [X_ELASTIC_INTERNAL_ORIGIN_REQUEST]: 'ftr',
+        },
+        body: { telemetry: { labels } },
+      });
+    } catch (error) {
+      const statusCode = (error as AxiosError).response?.status;
+      if (statusCode === 404) {
+        throw new Error(
+          `Failed to update labels, supported Kibana version is 8.11.0+ and must be started with "coreApp.allowDynamicConfigOverrides:true"`
+        );
+      } else throw error;
+    }
   }
 
   private async setupApm() {
@@ -385,7 +393,7 @@ export class JourneyFtrHarness {
     }
 
     const isServerlessProject = !!this.config.get('serverless');
-    const kibanaPage = getNewPageObject(isServerlessProject, page, this.log);
+    const kibanaPage = getNewPageObject(isServerlessProject, page, this.log, this.retry);
 
     this.#_ctx = this.journeyConfig.getExtendedStepCtx({
       kibanaPage,
diff --git a/packages/kbn-journeys/services/auth.ts b/packages/kbn-journeys/services/auth.ts
index 2a2a9719bd49b..1bc015d9f81cc 100644
--- a/packages/kbn-journeys/services/auth.ts
+++ b/packages/kbn-journeys/services/auth.ts
@@ -10,9 +10,7 @@ import Url from 'url';
 import { format } from 'util';
 
 import axios, { AxiosResponse } from 'axios';
-import { ToolingLog } from '@kbn/tooling-log';
-import { Config } from '@kbn/test';
-import { KibanaServer } from '@kbn/ftr-common-functional-services';
+import { FtrService } from './ftr_context_provider';
 
 export interface Credentials {
   username: string;
@@ -22,12 +20,10 @@ export interface Credentials {
 function extractCookieValue(authResponse: AxiosResponse) {
   return authResponse.headers['set-cookie']?.[0].toString().split(';')[0].split('sid=')[1] ?? '';
 }
-export class Auth {
-  constructor(
-    private readonly config: Config,
-    private readonly log: ToolingLog,
-    private readonly kibanaServer: KibanaServer
-  ) {}
+export class AuthService extends FtrService {
+  private readonly config = this.ctx.getService('config');
+  private readonly log = this.ctx.getService('log');
+  private readonly kibanaServer = this.ctx.getService('kibanaServer');
 
   public async login(credentials?: Credentials) {
     const baseUrl = new URL(
diff --git a/packages/kbn-journeys/services/es.ts b/packages/kbn-journeys/services/es.ts
new file mode 100644
index 0000000000000..16ca3079a2ab0
--- /dev/null
+++ b/packages/kbn-journeys/services/es.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 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 { Client } from '@elastic/elasticsearch';
+
+import { createEsClientForFtrConfig, ProvidedType } from '@kbn/test';
+import { FtrProviderContext } from './ftr_context_provider';
+
+export function EsProvider({ getService }: FtrProviderContext): Client {
+  const config = getService('config');
+
+  return createEsClientForFtrConfig(config);
+}
+
+export type Es = ProvidedType<typeof EsProvider>;
diff --git a/packages/kbn-journeys/services/ftr_context_provider.ts b/packages/kbn-journeys/services/ftr_context_provider.ts
new file mode 100644
index 0000000000000..7dd5038ef3f19
--- /dev/null
+++ b/packages/kbn-journeys/services/ftr_context_provider.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 { GenericFtrProviderContext, GenericFtrService } from '@kbn/test';
+import { services } from '.';
+
+export type FtrProviderContext = GenericFtrProviderContext<typeof services, {}>;
+export class FtrService extends GenericFtrService<FtrProviderContext> {}
diff --git a/packages/kbn-journeys/services/index.ts b/packages/kbn-journeys/services/index.ts
new file mode 100644
index 0000000000000..93611e5d5a3f8
--- /dev/null
+++ b/packages/kbn-journeys/services/index.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 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 { commonFunctionalServices, RetryService } from '@kbn/ftr-common-functional-services';
+import { EsArchiverProvider } from '@kbn/ftr-common-functional-services/services/es_archiver';
+import { KibanaServerProvider } from '@kbn/ftr-common-functional-services/services/kibana_server';
+import { ProvidedType } from '@kbn/test';
+import { EsProvider } from './es';
+import { AuthService } from './auth';
+
+export const services = {
+  es: EsProvider,
+  kibanaServer: commonFunctionalServices.kibanaServer,
+  esArchiver: commonFunctionalServices.esArchiver,
+  retry: commonFunctionalServices.retry,
+  auth: AuthService,
+};
+
+export type EsArchiver = ProvidedType<typeof EsArchiverProvider>;
+export type KibanaServer = ProvidedType<typeof KibanaServerProvider>;
+export type Es = ProvidedType<typeof EsProvider>;
+export type Auth = AuthService;
+export type Retry = RetryService;
diff --git a/packages/kbn-journeys/services/page/index.ts b/packages/kbn-journeys/services/page/index.ts
index 6a809eb7480f6..6e0aaafcfdc27 100644
--- a/packages/kbn-journeys/services/page/index.ts
+++ b/packages/kbn-journeys/services/page/index.ts
@@ -8,9 +8,10 @@
 
 import { ToolingLog } from '@kbn/tooling-log';
 import { Page } from 'playwright';
+import { Retry } from '..';
 import { KibanaPage } from './kibana_page';
 import { ProjectPage } from './project_page';
 
-export function getNewPageObject(isServerless: boolean, page: Page, log: ToolingLog) {
-  return isServerless ? new ProjectPage(page, log) : new KibanaPage(page, log);
+export function getNewPageObject(isServerless: boolean, page: Page, log: ToolingLog, retry: Retry) {
+  return isServerless ? new ProjectPage(page, log, retry) : new KibanaPage(page, log, retry);
 }
diff --git a/packages/kbn-journeys/services/page/kibana_page.ts b/packages/kbn-journeys/services/page/kibana_page.ts
index 72e595601473a..170e009d0cd29 100644
--- a/packages/kbn-journeys/services/page/kibana_page.ts
+++ b/packages/kbn-journeys/services/page/kibana_page.ts
@@ -9,6 +9,7 @@
 import { subj } from '@kbn/test-subj-selector';
 import { ToolingLog } from '@kbn/tooling-log';
 import { Page } from 'playwright';
+import { Retry } from '..';
 
 interface WaitForRenderArgs {
   expectedItemsCount: number;
@@ -19,10 +20,12 @@ interface WaitForRenderArgs {
 export class KibanaPage {
   readonly page: Page;
   readonly log: ToolingLog;
+  readonly retry: Retry;
 
-  constructor(page: Page, log: ToolingLog) {
+  constructor(page: Page, log: ToolingLog, retry: Retry) {
     this.page = page;
     this.log = log;
+    this.retry = retry;
   }
 
   async waitForHeader() {
@@ -36,25 +39,32 @@ export class KibanaPage {
   }
 
   async waitForRender({ expectedItemsCount, itemLocator, checkAttribute }: WaitForRenderArgs) {
-    try {
-      await this.page.waitForFunction(
-        function renderCompleted(args: WaitForRenderArgs) {
-          const renderingItems = Array.from(document.querySelectorAll(args.itemLocator));
-          const allItemsLoaded = renderingItems.length === args.expectedItemsCount;
-          return allItemsLoaded
-            ? renderingItems.every((e) => e.getAttribute(args.checkAttribute) === 'true')
-            : false;
-        },
-        { expectedItemsCount, itemLocator, checkAttribute }
-      );
-    } catch (err) {
-      const loaded = await this.page.$$(itemLocator);
-      const rendered = await this.page.$$(`${itemLocator}[${checkAttribute}="true"]`);
-      this.log.error(
-        `'waitForRendering' failed: loaded - ${loaded.length}, rendered - ${rendered.length}, expected count - ${expectedItemsCount}`
-      );
-      throw err;
-    }
+    // we can't use `page.waitForFunction` because of CSP while testing on Cloud
+    await this.retry.waitFor(
+      `rendering of ${expectedItemsCount} elements with selector ${itemLocator} is completed`,
+      async () => {
+        const renderingItems = await this.page.$$(itemLocator);
+        if (renderingItems.length === expectedItemsCount) {
+          // all components are loaded, checking if all are rendered
+          const renderStatuses = await Promise.all(
+            renderingItems.map(async (item) => {
+              return (await item.getAttribute(checkAttribute)) === 'true';
+            })
+          );
+          const rendered = renderStatuses.filter((isRendered) => isRendered === true);
+          this.log.debug(
+            `waitForRender: ${rendered.length} out of ${expectedItemsCount} are rendered...`
+          );
+          return rendered.length === expectedItemsCount;
+        } else {
+          // not all components are loaded yet
+          this.log.debug(
+            `waitForRender: ${renderingItems.length} out of ${expectedItemsCount} are loaded...`
+          );
+          return false;
+        }
+      }
+    );
   }
 
   async waitForVisualizations(count: number) {
diff --git a/x-pack/performance/journeys/many_fields_discover.ts b/x-pack/performance/journeys/many_fields_discover.ts
index a37207f6e092d..2a801dea4478f 100644
--- a/x-pack/performance/journeys/many_fields_discover.ts
+++ b/x-pack/performance/journeys/many_fields_discover.ts
@@ -13,7 +13,11 @@ export const journey = new Journey({
   esArchives: ['test/functional/fixtures/es_archiver/many_fields'],
 })
   .step('Go to Discover Page', async ({ page, kbnUrl, kibanaPage }) => {
-    await page.goto(kbnUrl.get(`/app/discover`));
+    await page.goto(
+      kbnUrl.get(
+        `/app/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:now-15m,to:now))&_a=(columns:!(),filters:!(),index:'35796250-bb09-11ec-a8e4-a9868e049a39',interval:auto,query:(language:kuery,query:''),sort:!())`
+      )
+    );
     await kibanaPage.waitForHeader();
     await page.waitForSelector('[data-test-subj="discoverDocTable"][data-render-complete="true"]');
     await page.waitForSelector(subj('globalLoadingIndicator-hidden'));

From 046718c18f39d87a45846a2e7f48138d5581e13b Mon Sep 17 00:00:00 2001
From: James Gowdy <jgowdy@elastic.co>
Date: Thu, 28 Sep 2023 11:09:25 +0100
Subject: [PATCH 04/20] [Transforms] Fixing use full data button (#167404)

Fixing bug introduced by a merge clash between
https://github.com/elastic/kibana/pull/166622 and
https://github.com/elastic/kibana/pull/166651

The `hideFrozenDataTierChoice` prop is no longer available in the
`FullTimeRangeSelector` component and so the transforms plugin needs to
set `showFrozenDataTierChoice` in the data picker context.
---
 .../components/full_time_range_selector.tsx   | 27 ++++++++++++-------
 .../components/wizard/wizard.tsx              |  7 +++--
 2 files changed, 23 insertions(+), 11 deletions(-)

diff --git a/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx b/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx
index 20e4ca43e233b..5b2a9d880c1b8 100644
--- a/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx
+++ b/x-pack/packages/ml/date_picker/src/components/full_time_range_selector.tsx
@@ -109,7 +109,9 @@ export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = (props) =>
         toasts,
         http,
         query,
-        showFrozenDataTierChoice ? frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE : false,
+        showFrozenDataTierChoice === false
+          ? false
+          : frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE,
         apiPath
       );
       if (typeof callback === 'function' && fullTimeRange !== undefined) {
@@ -192,9 +194,16 @@ export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = (props) =>
     [sortOptions, frozenDataPreference, setPreference]
   );
 
-  const buttonTooltip = useMemo(
-    () =>
-      frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE ? (
+  const buttonTooltip = useMemo(() => {
+    if (showFrozenDataTierChoice === false) {
+      return (
+        <FormattedMessage
+          id="xpack.ml.datePicker.fullTimeRangeSelector.useFullData"
+          defaultMessage="Use full range of data."
+        />
+      );
+    } else {
+      return frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE ? (
         <FormattedMessage
           id="xpack.ml.datePicker.fullTimeRangeSelector.useFullDataExcludingFrozenButtonTooltip"
           defaultMessage="Use full range of data excluding frozen data tier."
@@ -204,9 +213,9 @@ export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = (props) =>
           id="xpack.ml.datePicker.fullTimeRangeSelector.useFullDataIncludingFrozenButtonTooltip"
           defaultMessage="Use full range of data including frozen data tier, which might have slower search results."
         />
-      ),
-    [frozenDataPreference]
-  );
+      );
+    }
+  }, [frozenDataPreference, showFrozenDataTierChoice]);
 
   return (
     <EuiFlexGroup responsive={false} gutterSize="xs">
@@ -222,7 +231,7 @@ export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = (props) =>
           />
         </EuiButton>
       </EuiToolTip>
-      {showFrozenDataTierChoice ? (
+      {showFrozenDataTierChoice === false ? null : (
         <EuiFlexItem grow={false}>
           <EuiPopover
             id={'mlFullTimeRangeSelectorOption'}
@@ -248,7 +257,7 @@ export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = (props) =>
             {popoverContent}
           </EuiPopover>
         </EuiFlexItem>
-      ) : null}
+      )}
     </EuiFlexGroup>
   );
 };
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx
index a26866452ff3f..b8ee028682bae 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx
@@ -12,7 +12,7 @@ import { EuiSteps, EuiStepStatus } from '@elastic/eui';
 
 import { i18n } from '@kbn/i18n';
 import { DataView } from '@kbn/data-views-plugin/public';
-import { DatePickerContextProvider } from '@kbn/ml-date-picker';
+import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker';
 import { Storage } from '@kbn/kibana-utils-plugin/public';
 import { StorageContextProvider } from '@kbn/ml-local-storage';
 import { UrlStateProvider } from '@kbn/ml-url-state';
@@ -20,6 +20,7 @@ import { UI_SETTINGS } from '@kbn/data-plugin/common';
 import type { FieldStatsServices } from '@kbn/unified-field-list/src/components/field_stats';
 import type { RuntimeMappings } from '@kbn/ml-runtime-field-utils';
 
+import { useEnabledFeatures } from '../../../../serverless_context';
 import type { TransformConfigUnion } from '../../../../../../common/types/transform';
 
 import { getCreateTransformRequestBody } from '../../../../common';
@@ -106,6 +107,7 @@ export const CreateTransformWizardContext = createContext<{
 });
 
 export const Wizard: FC<WizardProps> = React.memo(({ cloneConfig, searchItems }) => {
+  const { showNodeInfo } = useEnabledFeatures();
   const appDependencies = useAppDependencies();
   const {
     ml: { FieldStatsFlyoutProvider },
@@ -226,9 +228,10 @@ export const Wizard: FC<WizardProps> = React.memo(({ cloneConfig, searchItems })
 
   const stepsConfig = [stepDefine, stepDetails, stepCreate];
 
-  const datePickerDeps = {
+  const datePickerDeps: DatePickerDependencies = {
     ...pick(appDependencies, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']),
     uiSettingsKeys: UI_SETTINGS,
+    showFrozenDataTierChoice: showNodeInfo,
   };
 
   const fieldStatsServices: FieldStatsServices = useMemo(

From f8dae67d8d9fc0f41d7f4d5317e596a7fe828dfe Mon Sep 17 00:00:00 2001
From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com>
Date: Thu, 28 Sep 2023 12:10:39 +0200
Subject: [PATCH 05/20] fix telemetry test (#167471)

Closes https://github.com/elastic/kibana/issues/156245

One test agent went from offline to inactive due to inactivity timeout
and broke the tests, instead changed the `last_checkin` to now-6m so it
stays in offline state.
---
 .../server/integration_tests/fleet_usage_telemetry.test.ts   | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts b/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts
index b841c641c3af4..e2e7e9f7887e6 100644
--- a/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts
+++ b/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts
@@ -20,8 +20,7 @@ import { waitForFleetSetup } from './helpers';
 
 const logFilePath = path.join(__dirname, 'logs.log');
 
-// Failing: See https://github.com/elastic/kibana/issues/156245
-describe.skip('fleet usage telemetry', () => {
+describe('fleet usage telemetry', () => {
   let core: any;
   let esServer: TestElasticsearchUtils;
   let kbnServer: TestKibanaUtils;
@@ -218,7 +217,7 @@ describe.skip('fleet usage telemetry', () => {
             version: '8.6.0',
           },
           last_checkin_status: 'online',
-          last_checkin: '2023-09-13T12:26:24Z',
+          last_checkin: new Date(Date.now() - 1000 * 60 * 6).toISOString(),
           active: true,
           policy_id: 'policy2',
         },

From 3be21c9e562dbc420d394c253995b696bb4e3147 Mon Sep 17 00:00:00 2001
From: Marco Antonio Ghiani <marcoantonio.ghiani01@gmail.com>
Date: Thu, 28 Sep 2023 12:21:35 +0200
Subject: [PATCH 06/20] [Log Explorer] Implement Data Views tab into selector
 (#166938)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

## 📓 Summary

Closes #166848

This work adds a new tab to navigate Data View from the Log Explorer
selector.
In this first iteration, when the user selects a data view, we move into
discovering preselecting and loading the data view of choice.

**N.B.**: this recording is made on a setup where I have no installed
integrations, so having the no integrations panel is the expected
behaviour.


https://github.com/elastic/kibana/assets/34506779/e8d1f622-86fb-4841-b4cc-4a913067d2cc

## Updated selector state machine

<img width="1492" alt="Screenshot 2023-09-22 at 12 15 44"
src="https://github.com/elastic/kibana/assets/34506779/c563b765-6c6c-41e8-b8cd-769c518932c3">

## New DataViews state machine

<img width="995" alt="Screenshot 2023-09-22 at 12 39 09"
src="https://github.com/elastic/kibana/assets/34506779/e4e43343-c915-42d8-8660-a2ee89f8d595">

---------

Co-authored-by: Marco Antonio Ghiani <marcoantonio.ghiani@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
 .../common/dataset_selection/index.ts         |   2 +
 .../components/dataset_selector/constants.tsx |  23 ++-
 .../dataset_selector.stories.tsx              |  66 +++++++-
 .../dataset_selector/dataset_selector.tsx     |  91 ++++++++--
 .../state_machine/state_machine.ts            |  80 +++++++--
 .../dataset_selector/state_machine/types.ts   |  28 ++-
 .../state_machine/use_dataset_selector.ts     |  31 +++-
 .../sub_components/data_views_panel_title.tsx |  21 +++
 .../sub_components/list_status.tsx            |   3 +-
 .../components/dataset_selector/types.ts      |  49 ++++--
 .../components/dataset_selector/utils.tsx     |  18 ++
 .../components/log_explorer/log_explorer.tsx  |  27 +--
 .../custom_dataset_selector.tsx               |  44 ++++-
 .../customizations/log_explorer_profile.tsx   |  10 +-
 .../public/hooks/use_data_views.tsx           | 104 ++++++++++++
 x-pack/plugins/log_explorer/public/plugin.ts  |   5 +-
 .../public/state_machines/data_views/index.ts |   8 +
 .../state_machines/data_views/src/defaults.ts |  20 +++
 .../state_machines/data_views/src/index.ts    |   9 +
 .../data_views/src/state_machine.ts           | 160 ++++++++++++++++++
 .../state_machines/data_views/src/types.ts    | 101 +++++++++++
 x-pack/plugins/log_explorer/public/types.ts   |   4 +-
 .../public/utils/get_data_view_test_subj.ts   |  20 +++
 .../public/utils/parse_data_view_list_item.ts |  15 ++
 .../common/translations.ts                    |   2 +-
 .../dataset_selector.ts                       | 153 ++++++++++++++++-
 .../observability_log_explorer.ts             |  12 ++
 .../dataset_selector.ts                       | 147 +++++++++++++++-
 28 files changed, 1161 insertions(+), 92 deletions(-)
 create mode 100644 x-pack/plugins/log_explorer/public/components/dataset_selector/sub_components/data_views_panel_title.tsx
 create mode 100644 x-pack/plugins/log_explorer/public/hooks/use_data_views.tsx
 create mode 100644 x-pack/plugins/log_explorer/public/state_machines/data_views/index.ts
 create mode 100644 x-pack/plugins/log_explorer/public/state_machines/data_views/src/defaults.ts
 create mode 100644 x-pack/plugins/log_explorer/public/state_machines/data_views/src/index.ts
 create mode 100644 x-pack/plugins/log_explorer/public/state_machines/data_views/src/state_machine.ts
 create mode 100644 x-pack/plugins/log_explorer/public/state_machines/data_views/src/types.ts
 create mode 100644 x-pack/plugins/log_explorer/public/utils/get_data_view_test_subj.ts
 create mode 100644 x-pack/plugins/log_explorer/public/utils/parse_data_view_list_item.ts

diff --git a/x-pack/plugins/log_explorer/common/dataset_selection/index.ts b/x-pack/plugins/log_explorer/common/dataset_selection/index.ts
index 3284610f53bcc..f390f7a89f87c 100644
--- a/x-pack/plugins/log_explorer/common/dataset_selection/index.ts
+++ b/x-pack/plugins/log_explorer/common/dataset_selection/index.ts
@@ -5,6 +5,7 @@
  * 2.0.
  */
 
+import { DataViewListItem } from '@kbn/data-views-plugin/common';
 import { AllDatasetSelection } from './all_dataset_selection';
 import { SingleDatasetSelection } from './single_dataset_selection';
 import { UnresolvedDatasetSelection } from './unresolved_dataset_selection';
@@ -14,6 +15,7 @@ export type DatasetSelection =
   | SingleDatasetSelection
   | UnresolvedDatasetSelection;
 export type DatasetSelectionChange = (datasetSelection: DatasetSelection) => void;
+export type DataViewSelection = (dataView: DataViewListItem) => void;
 
 export const isDatasetSelection = (input: any): input is DatasetSelection => {
   return (
diff --git a/x-pack/plugins/log_explorer/public/components/dataset_selector/constants.tsx b/x-pack/plugins/log_explorer/public/components/dataset_selector/constants.tsx
index 1cbcd6a0f032a..28a5401f98048 100644
--- a/x-pack/plugins/log_explorer/public/components/dataset_selector/constants.tsx
+++ b/x-pack/plugins/log_explorer/public/components/dataset_selector/constants.tsx
@@ -12,8 +12,10 @@ export const INTEGRATIONS_PANEL_ID = 'dataset-selector-integrations-panel';
 export const INTEGRATIONS_TAB_ID = 'dataset-selector-integrations-tab';
 export const UNCATEGORIZED_PANEL_ID = 'dataset-selector-uncategorized-panel';
 export const UNCATEGORIZED_TAB_ID = 'dataset-selector-uncategorized-tab';
+export const DATA_VIEWS_PANEL_ID = 'dataset-selector-data-views-panel';
+export const DATA_VIEWS_TAB_ID = 'dataset-selector-data-views-tab';
 
-export const DATA_VIEW_POPOVER_CONTENT_WIDTH = 300;
+export const DATA_VIEW_POPOVER_CONTENT_WIDTH = 400;
 
 export const showAllLogsLabel = i18n.translate('xpack.logExplorer.datasetSelector.showAllLogs', {
   defaultMessage: 'Show all logs',
@@ -28,6 +30,14 @@ export const uncategorizedLabel = i18n.translate(
   { defaultMessage: 'Uncategorized' }
 );
 
+export const dataViewsLabel = i18n.translate('xpack.logExplorer.datasetSelector.dataViews', {
+  defaultMessage: 'Data Views',
+});
+
+export const openDiscoverLabel = i18n.translate('xpack.logExplorer.datasetSelector.openDiscover', {
+  defaultMessage: 'Opens in Discover',
+});
+
 export const sortOrdersLabel = i18n.translate('xpack.logExplorer.datasetSelector.sortOrders', {
   defaultMessage: 'Sort directions',
 });
@@ -43,6 +53,17 @@ export const noDatasetsDescriptionLabel = i18n.translate(
   }
 );
 
+export const noDataViewsLabel = i18n.translate('xpack.logExplorer.datasetSelector.noDataViews', {
+  defaultMessage: 'No data views found',
+});
+
+export const noDataViewsDescriptionLabel = i18n.translate(
+  'xpack.logExplorer.datasetSelector.noDataViewsDescription',
+  {
+    defaultMessage: 'No data views or search results found.',
+  }
+);
+
 export const noIntegrationsLabel = i18n.translate(
   'xpack.logExplorer.datasetSelector.noIntegrations',
   { defaultMessage: 'No integrations found' }
diff --git a/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.stories.tsx b/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.stories.tsx
index 10bc958c8f2ce..82178164994eb 100644
--- a/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.stories.tsx
+++ b/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.stories.tsx
@@ -11,6 +11,7 @@ import React, { useState } from 'react';
 import { I18nProvider } from '@kbn/i18n-react';
 import type { Meta, Story } from '@storybook/react';
 import { IndexPattern } from '@kbn/io-ts-utils';
+import { DataViewListItem } from '@kbn/data-views-plugin/common';
 import {
   AllDatasetSelection,
   DatasetSelection,
@@ -29,6 +30,10 @@ const meta: Meta<typeof DatasetSelector> = {
       options: [null, { message: 'Failed to fetch data streams' }],
       control: { type: 'radio' },
     },
+    dataViewsError: {
+      options: [null, { message: 'Failed to fetch data data views' }],
+      control: { type: 'radio' },
+    },
     integrationsError: {
       options: [null, { message: 'Failed to fetch data integrations' }],
       control: { type: 'radio' },
@@ -71,20 +76,39 @@ const DatasetSelectorTemplate: Story<DatasetSelectorProps> = (args) => {
 
   const sortedDatasets = search.sortOrder === 'asc' ? filteredDatasets : filteredDatasets.reverse();
 
+  const filteredDataViews = mockDataViews.filter((dataView) =>
+    dataView.name?.includes(search.name as string)
+  );
+
+  const sortedDataViews =
+    search.sortOrder === 'asc' ? filteredDataViews : filteredDataViews.reverse();
+
+  const {
+    datasetsError,
+    dataViewsError,
+    integrationsError,
+    isLoadingDataViews,
+    isLoadingIntegrations,
+    isLoadingUncategorized,
+  } = args;
+
   return (
     <DatasetSelector
       {...args}
-      datasets={sortedDatasets}
+      datasets={Boolean(datasetsError || isLoadingUncategorized) ? [] : sortedDatasets}
+      dataViews={Boolean(dataViewsError || isLoadingDataViews) ? [] : sortedDataViews}
       datasetSelection={datasetSelection}
-      integrations={sortedIntegrations}
+      integrations={Boolean(integrationsError || isLoadingIntegrations) ? [] : sortedIntegrations}
+      onDataViewsSearch={setSearch}
+      onDataViewsSort={setSearch}
       onIntegrationsLoadMore={onIntegrationsLoadMore}
       onIntegrationsSearch={setSearch}
       onIntegrationsSort={setSearch}
       onIntegrationsStreamsSearch={setSearch}
       onIntegrationsStreamsSort={setSearch}
       onSelectionChange={onSelectionChange}
-      onUnmanagedStreamsSearch={setSearch}
-      onUnmanagedStreamsSort={setSearch}
+      onUncategorizedSearch={setSearch}
+      onUncategorizedSort={setSearch}
     />
   );
 };
@@ -92,12 +116,18 @@ const DatasetSelectorTemplate: Story<DatasetSelectorProps> = (args) => {
 export const Basic = DatasetSelectorTemplate.bind({});
 Basic.args = {
   datasetsError: null,
+  dataViewsError: null,
   integrationsError: null,
+  isLoadingDataViews: false,
   isLoadingIntegrations: false,
-  isLoadingStreams: false,
+  isLoadingUncategorized: false,
+  isSearchingIntegrations: false,
+  onDataViewsReload: () => alert('Reload data views...'),
+  onDataViewSelection: (dataView) => alert(`Navigate to data view "${dataView.name}"`),
+  onDataViewsTabClick: () => console.log('Load data views...'),
   onIntegrationsReload: () => alert('Reload integrations...'),
-  onStreamsEntryClick: () => console.log('Load uncategorized streams...'),
-  onUnmanagedStreamsReload: () => alert('Reloading streams...'),
+  onUncategorizedTabClick: () => console.log('Load uncategorized streams...'),
+  onUncategorizedReload: () => alert('Reloading streams...'),
 };
 
 const mockIntegrations: Integration[] = [
@@ -477,3 +507,25 @@ const mockDatasets: Dataset[] = [
   { name: 'data-load-balancing-logs-*' as IndexPattern },
   { name: 'data-scaling-logs-*' as IndexPattern },
 ].map((dataset) => Dataset.create(dataset));
+
+const mockDataViews: DataViewListItem[] = [
+  {
+    id: 'logs-*',
+    namespaces: ['default'],
+    title: 'logs-*',
+    name: 'logs-*',
+  },
+  {
+    id: 'metrics-*',
+    namespaces: ['default'],
+    title: 'metrics-*',
+    name: 'metrics-*',
+  },
+  {
+    id: '7258d186-6430-4b51-bb67-2603cdfb4652',
+    namespaces: ['default'],
+    title: 'synthetics-*',
+    typeMeta: {},
+    name: 'synthetics-dashboard',
+  },
+];
diff --git a/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.tsx b/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.tsx
index f9e722effc784..10d8b4c046c9a 100644
--- a/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.tsx
+++ b/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.tsx
@@ -10,6 +10,9 @@ import styled from '@emotion/styled';
 import { EuiContextMenu, EuiHorizontalRule, EuiTab, EuiTabs } from '@elastic/eui';
 import { useIntersectionRef } from '../../hooks/use_intersection_ref';
 import {
+  dataViewsLabel,
+  DATA_VIEWS_PANEL_ID,
+  DATA_VIEWS_TAB_ID,
   DATA_VIEW_POPOVER_CONTENT_WIDTH,
   integrationsLabel,
   INTEGRATIONS_PANEL_ID,
@@ -25,19 +28,30 @@ import { SelectorActions } from './sub_components/selector_actions';
 import { DatasetSelectorProps } from './types';
 import {
   buildIntegrationsTree,
+  createDataViewsStatusItem,
   createIntegrationStatusItem,
   createUncategorizedStatusItem,
 } from './utils';
+import { getDataViewTestSubj } from '../../utils/get_data_view_test_subj';
+import { DataViewsPanelTitle } from './sub_components/data_views_panel_title';
 
 export function DatasetSelector({
   datasets,
-  datasetsError,
   datasetSelection,
+  datasetsError,
+  dataViews,
+  dataViewsError,
   integrations,
   integrationsError,
+  isLoadingDataViews,
   isLoadingIntegrations,
-  isLoadingStreams,
+  isLoadingUncategorized,
   isSearchingIntegrations,
+  onDataViewSelection,
+  onDataViewsReload,
+  onDataViewsSearch,
+  onDataViewsSort,
+  onDataViewsTabClick,
   onIntegrationsLoadMore,
   onIntegrationsReload,
   onIntegrationsSearch,
@@ -45,10 +59,10 @@ export function DatasetSelector({
   onIntegrationsStreamsSearch,
   onIntegrationsStreamsSort,
   onSelectionChange,
-  onStreamsEntryClick,
-  onUnmanagedStreamsReload,
-  onUnmanagedStreamsSearch,
-  onUnmanagedStreamsSort,
+  onUncategorizedReload,
+  onUncategorizedSearch,
+  onUncategorizedSort,
+  onUncategorizedTabClick,
 }: DatasetSelectorProps) {
   const {
     panelId,
@@ -62,21 +76,26 @@ export function DatasetSelector({
     searchByName,
     selectAllLogDataset,
     selectDataset,
+    selectDataView,
     sortByOrder,
     switchToIntegrationsTab,
     switchToUncategorizedTab,
+    switchToDataViewsTab,
     togglePopover,
   } = useDatasetSelector({
     initialContext: { selection: datasetSelection },
+    onDataViewSelection,
+    onDataViewsSearch,
+    onDataViewsSort,
     onIntegrationsLoadMore,
     onIntegrationsReload,
     onIntegrationsSearch,
     onIntegrationsSort,
     onIntegrationsStreamsSearch,
     onIntegrationsStreamsSort,
-    onUnmanagedStreamsSearch,
-    onUnmanagedStreamsSort,
-    onUnmanagedStreamsReload,
+    onUncategorizedSearch,
+    onUncategorizedSort,
+    onUncategorizedReload,
     onSelectionChange,
   });
 
@@ -117,8 +136,8 @@ export function DatasetSelector({
         createUncategorizedStatusItem({
           data: datasets,
           error: datasetsError,
-          isLoading: isLoadingStreams,
-          onRetry: onUnmanagedStreamsReload,
+          isLoading: isLoadingUncategorized,
+          onRetry: onUncategorizedReload,
         }),
       ];
     }
@@ -127,7 +146,26 @@ export function DatasetSelector({
       name: dataset.title,
       onClick: () => selectDataset(dataset),
     }));
-  }, [datasets, datasetsError, isLoadingStreams, selectDataset, onUnmanagedStreamsReload]);
+  }, [datasets, datasetsError, isLoadingUncategorized, selectDataset, onUncategorizedReload]);
+
+  const dataViewsItems = useMemo(() => {
+    if (!dataViews || dataViews.length === 0) {
+      return [
+        createDataViewsStatusItem({
+          data: dataViews,
+          error: dataViewsError,
+          isLoading: isLoadingDataViews,
+          onRetry: onDataViewsReload,
+        }),
+      ];
+    }
+
+    return dataViews.map((dataView) => ({
+      'data-test-subj': getDataViewTestSubj(dataView.title),
+      name: dataView.name,
+      onClick: () => selectDataView(dataView),
+    }));
+  }, [dataViews, dataViewsError, isLoadingDataViews, selectDataView, onDataViewsReload]);
 
   const tabs = [
     {
@@ -140,11 +178,20 @@ export function DatasetSelector({
       id: UNCATEGORIZED_TAB_ID,
       name: uncategorizedLabel,
       onClick: () => {
-        onStreamsEntryClick(); // Lazy-load uncategorized datasets only when accessing the Uncategorized tab
+        onUncategorizedTabClick(); // Lazy-load uncategorized datasets only when accessing the Uncategorized tab
         switchToUncategorizedTab();
       },
       'data-test-subj': 'datasetSelectorUncategorizedTab',
     },
+    {
+      id: DATA_VIEWS_TAB_ID,
+      name: dataViewsLabel,
+      onClick: () => {
+        onDataViewsTabClick(); // Lazy-load data views only when accessing the Data Views tab
+        switchToDataViewsTab();
+      },
+      'data-test-subj': 'datasetSelectorDataViewsTab',
+    },
   ];
 
   const tabEntries = tabs.map((tab) => (
@@ -175,7 +222,7 @@ export function DatasetSelector({
         search={search}
         onSearch={searchByName}
         onSort={sortByOrder}
-        isLoading={isSearchingIntegrations || isLoadingStreams}
+        isLoading={isSearchingIntegrations || isLoadingUncategorized}
       />
       <EuiHorizontalRule margin="none" />
       {/* For a smoother user experience, we keep each tab content mount and we only show the select one
@@ -215,6 +262,22 @@ export function DatasetSelector({
         data-test-subj="uncategorizedContextMenu"
         size="s"
       />
+      {/* Data views tab content */}
+      <ContextMenu
+        hidden={tabId !== DATA_VIEWS_TAB_ID}
+        initialPanelId={DATA_VIEWS_PANEL_ID}
+        panels={[
+          {
+            id: DATA_VIEWS_PANEL_ID,
+            title: <DataViewsPanelTitle />,
+            width: DATA_VIEW_POPOVER_CONTENT_WIDTH,
+            items: dataViewsItems,
+          },
+        ]}
+        className="eui-yScroll"
+        data-test-subj="dataViewsContextMenu"
+        size="s"
+      />
     </DatasetsPopover>
   );
 }
diff --git a/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/state_machine.ts b/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/state_machine.ts
index 618a77b7acfea..5e5df10504ec3 100644
--- a/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/state_machine.ts
+++ b/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/state_machine.ts
@@ -7,7 +7,7 @@
 
 import { actions, assign, createMachine, raise } from 'xstate';
 import { AllDatasetSelection, SingleDatasetSelection } from '../../../../common/dataset_selection';
-import { INTEGRATIONS_TAB_ID, UNCATEGORIZED_TAB_ID } from '../constants';
+import { DATA_VIEWS_TAB_ID, INTEGRATIONS_TAB_ID, UNCATEGORIZED_TAB_ID } from '../constants';
 import { defaultSearch, DEFAULT_CONTEXT } from './defaults';
 import {
   DatasetsSelectorContext,
@@ -20,7 +20,7 @@ import {
 export const createPureDatasetsSelectorStateMachine = (
   initialContext: Partial<DefaultDatasetsSelectorContext> = DEFAULT_CONTEXT
 ) =>
-  /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVsztgZTABswBjdAewCcA6AB3PoDcwaTDzsIBiAFQHkA4gIAyAUQDaABgC6iUPVgBLdIvIA7OSAAeiAJwBmXdUkAWSZIAcAJgBs+gIy6A7PpsAaEAE9E+q-eoW+k5OVuY24ZJONgC+0R5omNi4BMRkVHQM5Mw0DGBqXADCwnx4EjKaCsqqGkjaen7UAKySjVYONoEmFk66Ht4IjU4m1B36Y4G6k7omVrHxGFg4+ESkFDT0TCzUufn8QmJSsrWVKuqaOgi69o3UTpZWznaSuoONfYitN5K29k6tNj1nvY5iAEotkis0utMtltrQ8lxSmICjwAPoAQWEwlRxQEeFRyHRPHRpR4hwqHCqZ1qFws3Wo+kkNkaFnsJhs90a+neCBs7OoVgeJl0NnsdmuJhBYKSy1Sawymxy8LU1EUanQYCglAw1VgPFQACNEQB1ACSPAKAAlUfxUQBVAByBSJogEfAASqaAFqiZA29EAIXJx0ppxqoAu9meDMawqszUsjRFvS8iHsUf8kRMUVCjXsrn0jSlCxlKVW6Q2WS2O1V6s12rDesN1EIilgKjUUFNda1OvUsEKlvRDoEolRAAVh6JhMH5KHquc09HbPGuq4bLoLEyLDy-v4XoY41FvvYLMXEksy1CFVWlXlaxre439QaW22O12ew3dYiCu6+FiNp8KipoOjwrrukSpp8A6+IBnwPD8AAsrOIAnAuNKIAAtIKNyDPoJgOOyhanim-SDFYIxXAY+F2H4MRxKCJaXpC8qVrCNZqo+379i+b7tmqn7cX2agDqU6LulaqIBgAmqiDrokhZRHHOShhouCBYYRXw2H4FgmAmQQGSYPJ8kY8YdFyTgWACTjAox0osXKFYwtWyoPvWIlNq+rYCZ23bCY2iIemismoh6yCiO6qHodSEbYQR+hNFY7IhD0fgSjyFiNHh3yRCKorOEE57grK5bQoqcL3lxnnPs2vkfgFtXVHg6CUGAqAALYDlaw6jhOU4zuUIZqRh8UIFG5m6bGgThJuTJvKmCB3Jmop-FZoRBAx8wXhCzkVbeVUqjVT66nxDWCU1p3qK17VdWJogSVJYUKUpMXznFdQDKeIzNPo+khLZrQ8jl5nXARG5BNMhglaWrEuZVnFfl553vpdyNhrdHXdcF7qhXJEVRe9o2fRcbROMYFiTA8jgivpVgg601DTOmfhtHSfKzA5zF7eVN4ce5J08aJqN+UJzU3W12MPciaKEsSpLE1S4ZfUm-ispMfJjJIjLTLu-3UIRALfM06a6dtTG7WV17sW594AK5qCQGCalQigAF6QC+JrmlJtqgeBAiQTw0Gwf6QbDapysaVpki3ARYRRi4KV8u4S1RgZxhCs03QuACsNOXztt3iqjvO4+buexA3viZJ1ovYpykUiTKsXDhQSG40zIhOTOV0Ty9hWNZ1AZ78035nmBe8zbrkl9QZcu1Alde4auP4+F7qRdFkdoR9rfYYKRjMqegLZmKXI7unzw3FYVMvDZOX5n8U-W2xs9HfPTuL8v1er0iogogJESEkogyQ71ivvBAVM46xlGBMW+LQyJphSkYYIhFBQpUFAZF+V55TYDlNUagShOzEERNOABaJMTYlxPieWICwEqV3i3GOjgkqrkwWMOwhE6QDxcMMdkJgJ6xlsOEHB8MaD4NWIQ4hUBSH-0AXQxW4C94xy5HHUwkQOh0QeGnfoYp+TZWzMbVcLIzzcytrg9IkiyCENQIQQgZDZZAIVqApW6lMKaXPgEfSSYuh+GcLGAeHJhgckmCYcJrgZimFiIxNQ5AIBwE0I5aeaxm7Rw8VhXSFMmR6QMuYIyi1+guGZmMKyx5HC-H0GI-a-MWBpPceNLCgwjA5NPHknWQxClplFIbUp-0hiMhSuyapRd35sA4JAepY0vqDyjDGeM0NdA00mDyXw6sgghDCBEKIIyZ6I2VFM0m2EuSrVyYZTpPIUoWBKYWQwIQWSDF+Lst++z7wAAt3yHMgQCa5m54y-EqSlEIqzXC9MLGYO4Nk77PIRodJGgUzqGi+THNcxhdJtPOcZHkgjrnhPTKeawe4+QwoOgLaqGNEWvg+e2ZFHibJotvnYXS24dYMyWkspKXRB4GXFIRHWJLalzyFijeqaN-IUv7LSxpoQbDGCjFTQRllsrxlMkMEeeTrKGE3GfAVxcP7Crqj5MV4trpqCxvdKVX0HiUR1kmEIXRbUslMnSEpAJu4cj+roXV78awLwrpQD2K8DSWouIIgUkgxQihcCyc2TgB6hDjkPQi-1mh2QMlU8xpVLESNYtMiBqjh4aKiDZXwOiB55gppNaG6aWTBAFdYsMRDBLEBDQlYUAoNxUyiDlQI3J05RCSmMQwZsDCuFsPW3N6hqB2MIK2ia1wR6xh6FGHt4MTL9ruMzX48YHhEucDE6IQA */
+  /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVsztgZTABswBjdAewCcA6AB3PoDcwaTDzsIBiAFQHkA4gIAyAUQDaABgC6iUPVgBLdIvIA7OSAAeiAIy6ATAGZqBgBwAWMwHYzdgwE4zD6wBoQAT0RGj16gDYzIwBWKwdJC2CDawMAX1j3NExsXAJiMio6BnJmGgYwNS4AYWE+PAkZTQVlVQ0kbUQbP0kDf0l-IwddSUkXYPcvBH99ah6jNqMzFrMDXSN4xIwsHHwiUgoaeiYWanzC-iExKVl66pV1TR0EO11qI0MLDosLXWsHWYHEUOCAmJ6u3QWBxWawLEBJZapNYZTbZXK7WgFLjlMRFHgAfQAgsJhOjSgI8OjkJieJjyjxjlUODULvUrmZ-H5rLopp0jO1rMEHP5PkMZndOT5JJ1ggy4glwUsUqt0hssts8oi1NRFGp0GAoJQMLVYDxUAAjZEAdQAkjwigAJdH8dEAVQAckUSaIBHwAEomgBaomQ1sxACFKadqec6qArgBaXT+H4GZ4RSS6YIxfwWSRuTx6OaSajWfwGVrRcIRIzixbJFZpdaZLY5HZ7FVqjVa0O6g3Gs2W618IkkzHogBqJtERsJpMDlWDSlDl0QswM1AsrTTDnuSZFFl5+nZufzxmeMSTorBEOlVZh8rrioKjfVmu16jb+uohEUsBUaigJqb99bxQtmL2gIojogACoBojCEG8ghrUs4IN0DimPmoRBP4-hOO0Zi8pytwOMEnRAtEbSzGYJ5SpW0JyrW8INqqd4tjqerPq+76ql+P6MY+yJFG6fA4t26ImvaPAum6JImnw9qEv6fA8PwACy0EgGccF0ogEYFj8yZGBY9yPCELIOLyyYLhhuirjpHSzP45EVlCso1nC9ZKrezYPmoT4vm+H4cQxHmwMioiYm6Xb+gAmui9qYgpFQnDB05qeGGl6T87SkZEYzWJEm6ZkMQKmMEgQEbYjLMnZkIytWsIKgiN70e5rbMd5bGft+-l-ng7oYhF6Lusgohuspqm0slCARrpJhRI8MRvLMSa5YMZjBNpLTptywwuL4FVnlRTm1XRnEBc1rG+e1jW1Hg6CUGAqAALaBZagHAWBEFQZOCU0mGDQIb0yEGKh4wYVMMY4YmozDJyJUGMKeY7ZRjk1VedXKg1v5MQaLVnUdoZXTd92BeUIVhZF0WxcNsGjT9wQsgEwTCpYKbWJyBgmVypjro8q5vHpDjww51WXrRrlo1xnknT57Hnej6h47dD3It16K9f1g0U4lVNXMYzTOO8XQONylis3lK0LsC+jzpMDJLvzVUXjRLn1TjGMsZLbXO7L13y4TkGiGivakuS6tffBXK3GYFncnp7LssCOGTIu4zWC09P6PmtkSqeCOCw717KgArmoJAYBqVCKAAXpAzEduaVo2sJokCOJPCSdJfoTvFKmU99kaOEhpGTIKdhdHmW7tCYJHjFM7TRrMtvntRzl59QhfF3eZeVxA1d4KateCcSpKDsOo7t8HM7qQgcYBOncYMxZRXWEYW6OD8BszEuq4uBYtjz3tSPCzeVeJcoAbyru2ImoUrS9TJnFKkGse4pSmAEKYBF0IFmyvhLcpYLDUFeMnYE7ReaMl-ojIWjsC5F2AaAre4ClYqzdANIaH0u7wPghGb+Zk8z5lmrHem-Q8rbn8NQIq612TrhjPMTOFEBb2yXijFelD16UArmAw0KI-YYgPmSUQFJmEjQQeNOMOC5hxnpq0IqrQtx6SEamF4S5SKOAsCQnOciGwQCWAORQYAADuT4a5dnriJMSEkpJjgDGfJKP1xhm3vn0bknRWRj2TMImGQQ0yhEMLoZxsiDquXcZgTxPi-E707HXHsDonSN3dF6H0p89Hd3goCO4bRIjJ2TjTFakhsICNMYuQIQ8lyzwzuWSqC99rIzcR4rxvjt7BUgcrUmMVYFThDhfYYOZ3hWEyT0FanIsGODuFMecycZhRDLJKeydtF65JvPk1AhSZm0LdD1SKqsmGd30fBdZdxHiBANsCTk7wtxzFuC8NBBskwuEcNk65Ey8lTKKbM1Emi+xHxHBEzWiAR4cycARLky0IiLT0HGBcekZiWFCOyGYMLMjYFlLUagShPzECCsirEAl8SEi0UHeprCL5RlXKMdk5hOhf0mO0LczMmR8OBPhdkjwnFSMuWMmgdL1gMqZVAFl6j-bcp0RigxEYohIQjoEbojgfDhGNoMaM4RcEvALKmEIQQug0tVVRBlqBCCEFZRogO2jdEfIafy6MPx0Lj1TEEdo3Itypj8M4VMDNHjT1BGCNQ5AIBwE0FnGRGw4GrLGhGbhEMMr01hpEeO1BVwhF8HmRMI9ghurIZQfN59C3JiQulFkmVy38JtcMRONabB6RaM8PmSrRl-2bdQNgHBICtsiVcNOJhojpheJZHwHw8pLhweMXwqZzB6Qtk23OKMF2YvGgRW4XaKVZQrXlOMZgq0+AIlC0UpkT2uNcgACx8uegxjIn1OABq8V42CYi8h8EI6OoR0xdL+eOkZu1SGnsOh1F2-62FWBMDentvgcq8heE+543RcLjEkFEVNSHs45LhU7dDj5mq-vfJhi+zNcE03Se0IqcwgS8neBPRMsMxHpjhhO5DLibmow9uLTGp0pYyfgCsttP1NLtFGN0ZwYLlrLQBryRkxjMq2FFYzLJ4maOwoAdJhjsnXatT8hdT2+MHqsbGo4BcwouQxCsJ50U+m7DPsZDGOt9NOifqkwotepdlGb2Yq51T5ghERzZAe5MGFME9NpuYA2HR8KwcfuFujyo7kPKfPFq4pZbjDCBCyZaPn8zPyTFWwE+EpjW2FE2tVZBImfP5cmeN0YI4tGrVarcIi7jxJBN-EFVGLmTtIV10MjL2LEHKxpXSnbIif3uBhaI0YtwgwCDDXw+hy3QvM7m2lHr1DUC9YQNbCEmu+GHZ-Dh+g+16Ajjg47zJjDLU6ZI+IQA */
   createMachine<DatasetsSelectorContext, DatasetsSelectorEvent, DatasetsSelectorTypestate>(
     {
       context: { ...DEFAULT_CONTEXT, ...initialContext },
@@ -55,6 +55,7 @@ export const createPureDatasetsSelectorStateMachine = (
                   entry: ['storeIntegrationsTabId'],
                   on: {
                     SWITCH_TO_UNCATEGORIZED_TAB: 'uncategorizedTab',
+                    SWITCH_TO_DATA_VIEWS_TAB: 'dataViewsTab',
                   },
                   states: {
                     hist: {
@@ -106,15 +107,37 @@ export const createPureDatasetsSelectorStateMachine = (
                   ],
                   on: {
                     SWITCH_TO_INTEGRATIONS_TAB: 'integrationsTab.hist',
+                    SWITCH_TO_DATA_VIEWS_TAB: 'dataViewsTab',
                     SEARCH_BY_NAME: {
-                      actions: ['storeSearch', 'searchUnmanagedStreams'],
+                      actions: ['storeSearch', 'searchUncategorized'],
                     },
                     SORT_BY_ORDER: {
-                      actions: ['storeSearch', 'sortUnmanagedStreams'],
+                      actions: ['storeSearch', 'sortUncategorized'],
                     },
                     SELECT_DATASET: '#closed',
                   },
                 },
+                dataViewsTab: {
+                  entry: [
+                    'storeDataViewsTabId',
+                    'retrieveSearchFromCache',
+                    'maybeRestoreSearchResult',
+                  ],
+                  on: {
+                    SWITCH_TO_INTEGRATIONS_TAB: 'integrationsTab.hist',
+                    SWITCH_TO_UNCATEGORIZED_TAB: 'uncategorizedTab',
+                    SEARCH_BY_NAME: {
+                      actions: ['storeSearch', 'searchDataViews'],
+                    },
+                    SORT_BY_ORDER: {
+                      actions: ['storeSearch', 'sortDataViews'],
+                    },
+                    SELECT_DATA_VIEW: {
+                      target: '#closed',
+                      actions: ['selectDataView'],
+                    },
+                  },
+                },
               },
             },
           },
@@ -149,12 +172,13 @@ export const createPureDatasetsSelectorStateMachine = (
       actions: {
         storeIntegrationsTabId: assign((_context) => ({ tabId: INTEGRATIONS_TAB_ID })),
         storeUncategorizedTabId: assign((_context) => ({ tabId: UNCATEGORIZED_TAB_ID })),
+        storeDataViewsTabId: assign((_context) => ({ tabId: DATA_VIEWS_TAB_ID })),
         storePanelId: assign((_context, event) =>
           'panelId' in event ? { panelId: event.panelId } : {}
         ),
         storeSearch: assign((context, event) => {
           if ('search' in event) {
-            const id = context.tabId === UNCATEGORIZED_TAB_ID ? context.tabId : context.panelId;
+            const id = context.tabId === INTEGRATIONS_TAB_ID ? context.panelId : context.tabId;
             context.searchCache.set(id, event.search);
 
             return {
@@ -179,6 +203,9 @@ export const createPureDatasetsSelectorStateMachine = (
           if (event.type === 'SWITCH_TO_UNCATEGORIZED_TAB' && 'tabId' in context) {
             return { search: context.searchCache.get(context.tabId) ?? defaultSearch };
           }
+          if (event.type === 'SWITCH_TO_DATA_VIEWS_TAB' && 'tabId' in context) {
+            return { search: context.searchCache.get(context.tabId) ?? defaultSearch };
+          }
           return {};
         }),
         maybeRestoreSearchResult: actions.pure((context, event) => {
@@ -188,8 +215,15 @@ export const createPureDatasetsSelectorStateMachine = (
             event.type === 'SWITCH_TO_INTEGRATIONS_TAB' && context.searchCache.has(context.panelId);
           const hasSearchOnUncategorizedTab =
             event.type === 'SWITCH_TO_UNCATEGORIZED_TAB' && context.searchCache.has(context.tabId);
+          const hasSearchOnDataViewsTab =
+            event.type === 'SWITCH_TO_DATA_VIEWS_TAB' && context.searchCache.has(context.tabId);
 
-          if (hasSearchOnChangePanel || hasSearchOnIntegrationsTab || hasSearchOnUncategorizedTab) {
+          if (
+            hasSearchOnChangePanel ||
+            hasSearchOnIntegrationsTab ||
+            hasSearchOnUncategorizedTab ||
+            hasSearchOnDataViewsTab
+          ) {
             return raise({ type: 'SORT_BY_ORDER', search: context.search });
           }
         }),
@@ -199,16 +233,19 @@ export const createPureDatasetsSelectorStateMachine = (
 
 export const createDatasetsSelectorStateMachine = ({
   initialContext,
+  onDataViewSelection,
+  onDataViewsSearch,
+  onDataViewsSort,
   onIntegrationsLoadMore,
   onIntegrationsReload,
   onIntegrationsSearch,
   onIntegrationsSort,
   onIntegrationsStreamsSearch,
   onIntegrationsStreamsSort,
-  onUnmanagedStreamsSearch,
-  onUnmanagedStreamsSort,
+  onUncategorizedSearch,
+  onUncategorizedSort,
   onSelectionChange,
-  onUnmanagedStreamsReload,
+  onUncategorizedReload,
 }: DatasetsSelectorStateMachineDependencies) =>
   createPureDatasetsSelectorStateMachine(initialContext).withConfig({
     actions: {
@@ -217,7 +254,12 @@ export const createDatasetsSelectorStateMachine = ({
       },
       loadMoreIntegrations: onIntegrationsLoadMore,
       relaodIntegrations: onIntegrationsReload,
-      reloadUnmanagedStreams: onUnmanagedStreamsReload,
+      reloadUncategorized: onUncategorizedReload,
+      selectDataView: (_context, event) => {
+        if (event.type === 'SELECT_DATA_VIEW' && 'dataView' in event) {
+          return onDataViewSelection(event.dataView);
+        }
+      },
       // Search actions
       searchIntegrations: (_context, event) => {
         if ('search' in event) {
@@ -229,6 +271,16 @@ export const createDatasetsSelectorStateMachine = ({
           onIntegrationsSort(event.search);
         }
       },
+      searchDataViews: (context, event) => {
+        if ('search' in event) {
+          onDataViewsSearch(event.search);
+        }
+      },
+      sortDataViews: (context, event) => {
+        if ('search' in event) {
+          onDataViewsSort(event.search);
+        }
+      },
       searchIntegrationsStreams: (context, event) => {
         if ('search' in event) {
           onIntegrationsStreamsSearch({ ...event.search, integrationId: context.panelId });
@@ -239,14 +291,14 @@ export const createDatasetsSelectorStateMachine = ({
           onIntegrationsStreamsSort({ ...event.search, integrationId: context.panelId });
         }
       },
-      searchUnmanagedStreams: (_context, event) => {
+      searchUncategorized: (_context, event) => {
         if ('search' in event) {
-          onUnmanagedStreamsSearch(event.search);
+          onUncategorizedSearch(event.search);
         }
       },
-      sortUnmanagedStreams: (_context, event) => {
+      sortUncategorized: (_context, event) => {
         if ('search' in event) {
-          onUnmanagedStreamsSort(event.search);
+          onUncategorizedSort(event.search);
         }
       },
     },
diff --git a/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/types.ts b/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/types.ts
index 859efecaf41a7..6ca09c9c2de07 100644
--- a/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/types.ts
+++ b/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/types.ts
@@ -4,7 +4,13 @@
  * 2.0; you may not use this file except in compliance with the Elastic License
  * 2.0.
  */
-import { DatasetSelection, DatasetSelectionChange } from '../../../../common/dataset_selection';
+import { DataViewListItem } from '@kbn/data-views-plugin/common';
+import { SearchDataViews } from '../../../hooks/use_data_views';
+import {
+  DatasetSelection,
+  DatasetSelectionChange,
+  DataViewSelection,
+} from '../../../../common/dataset_selection';
 import { Dataset } from '../../../../common/datasets/models/dataset';
 import { ReloadDatasets, SearchDatasets } from '../../../hooks/use_datasets';
 import {
@@ -56,6 +62,10 @@ export type DatasetsSelectorTypestate =
       value: 'popover.open.uncategorizedTab';
       context: DefaultDatasetsSelectorContext;
     }
+  | {
+      value: 'popover.open.dataViewsTab';
+      context: DefaultDatasetsSelectorContext;
+    }
   | {
       value: 'selection';
       context: DefaultDatasetsSelectorContext;
@@ -84,6 +94,9 @@ export type DatasetsSelectorEvent =
   | {
       type: 'SWITCH_TO_UNCATEGORIZED_TAB';
     }
+  | {
+      type: 'SWITCH_TO_DATA_VIEWS_TAB';
+    }
   | {
       type: 'CHANGE_PANEL';
       panelId: PanelId;
@@ -92,6 +105,10 @@ export type DatasetsSelectorEvent =
       type: 'SELECT_DATASET';
       dataset: Dataset;
     }
+  | {
+      type: 'SELECT_DATA_VIEW';
+      dataView: DataViewListItem;
+    }
   | {
       type: 'SELECT_ALL_LOGS_DATASET';
     }
@@ -109,6 +126,9 @@ export type DatasetsSelectorEvent =
 
 export interface DatasetsSelectorStateMachineDependencies {
   initialContext?: Partial<DefaultDatasetsSelectorContext>;
+  onDataViewSelection: DataViewSelection;
+  onDataViewsSearch: SearchDataViews;
+  onDataViewsSort: SearchDataViews;
   onIntegrationsLoadMore: LoadMoreIntegrations;
   onIntegrationsReload: ReloadIntegrations;
   onIntegrationsSearch: SearchIntegrations;
@@ -116,7 +136,7 @@ export interface DatasetsSelectorStateMachineDependencies {
   onIntegrationsStreamsSearch: SearchIntegrations;
   onIntegrationsStreamsSort: SearchIntegrations;
   onSelectionChange: DatasetSelectionChange;
-  onUnmanagedStreamsReload: ReloadDatasets;
-  onUnmanagedStreamsSearch: SearchDatasets;
-  onUnmanagedStreamsSort: SearchDatasets;
+  onUncategorizedReload: ReloadDatasets;
+  onUncategorizedSearch: SearchDatasets;
+  onUncategorizedSort: SearchDatasets;
 }
diff --git a/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/use_dataset_selector.ts b/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/use_dataset_selector.ts
index cdbed34c74278..125217e8decdd 100644
--- a/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/use_dataset_selector.ts
+++ b/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/use_dataset_selector.ts
@@ -11,6 +11,7 @@ import {
   ChangePanelHandler,
   DatasetSelectionHandler,
   DatasetsSelectorSearchHandler,
+  DataViewSelectionHandler,
   PanelId,
 } from '../types';
 import { createDatasetsSelectorStateMachine } from './state_machine';
@@ -18,6 +19,9 @@ import { DatasetsSelectorStateMachineDependencies } from './types';
 
 export const useDatasetSelector = ({
   initialContext,
+  onDataViewSelection,
+  onDataViewsSearch,
+  onDataViewsSort,
   onIntegrationsLoadMore,
   onIntegrationsReload,
   onIntegrationsSearch,
@@ -25,13 +29,16 @@ export const useDatasetSelector = ({
   onIntegrationsStreamsSearch,
   onIntegrationsStreamsSort,
   onSelectionChange,
-  onUnmanagedStreamsSearch,
-  onUnmanagedStreamsSort,
-  onUnmanagedStreamsReload,
+  onUncategorizedSearch,
+  onUncategorizedSort,
+  onUncategorizedReload,
 }: DatasetsSelectorStateMachineDependencies) => {
   const datasetsSelectorStateService = useInterpret(() =>
     createDatasetsSelectorStateMachine({
       initialContext,
+      onDataViewSelection,
+      onDataViewsSearch,
+      onDataViewsSort,
       onIntegrationsLoadMore,
       onIntegrationsReload,
       onIntegrationsSearch,
@@ -39,9 +46,9 @@ export const useDatasetSelector = ({
       onIntegrationsStreamsSearch,
       onIntegrationsStreamsSort,
       onSelectionChange,
-      onUnmanagedStreamsSearch,
-      onUnmanagedStreamsSort,
-      onUnmanagedStreamsReload,
+      onUncategorizedSearch,
+      onUncategorizedSort,
+      onUncategorizedReload,
     })
   );
 
@@ -64,6 +71,11 @@ export const useDatasetSelector = ({
     [datasetsSelectorStateService]
   );
 
+  const switchToDataViewsTab = useCallback(
+    () => datasetsSelectorStateService.send({ type: 'SWITCH_TO_DATA_VIEWS_TAB' }),
+    [datasetsSelectorStateService]
+  );
+
   const changePanel = useCallback<ChangePanelHandler>(
     (panelDetails) =>
       datasetsSelectorStateService.send({
@@ -93,6 +105,11 @@ export const useDatasetSelector = ({
     [datasetsSelectorStateService]
   );
 
+  const selectDataView = useCallback<DataViewSelectionHandler>(
+    (dataView) => datasetsSelectorStateService.send({ type: 'SELECT_DATA_VIEW', dataView }),
+    [datasetsSelectorStateService]
+  );
+
   const sortByOrder = useCallback<DatasetsSelectorSearchHandler>(
     (params) => datasetsSelectorStateService.send({ type: 'SORT_BY_ORDER', search: params }),
     [datasetsSelectorStateService]
@@ -124,9 +141,11 @@ export const useDatasetSelector = ({
     searchByName,
     selectAllLogDataset,
     selectDataset,
+    selectDataView,
     sortByOrder,
     switchToIntegrationsTab,
     switchToUncategorizedTab,
+    switchToDataViewsTab,
     togglePopover,
   };
 };
diff --git a/x-pack/plugins/log_explorer/public/components/dataset_selector/sub_components/data_views_panel_title.tsx b/x-pack/plugins/log_explorer/public/components/dataset_selector/sub_components/data_views_panel_title.tsx
new file mode 100644
index 0000000000000..401a1728f9a7c
--- /dev/null
+++ b/x-pack/plugins/log_explorer/public/components/dataset_selector/sub_components/data_views_panel_title.tsx
@@ -0,0 +1,21 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { dataViewsLabel, openDiscoverLabel } from '../constants';
+
+export const DataViewsPanelTitle = () => {
+  return (
+    <EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
+      <EuiFlexItem>{dataViewsLabel}</EuiFlexItem>
+      <EuiFlexItem grow={false}>
+        <EuiBadge iconType="discoverApp">{openDiscoverLabel}</EuiBadge>
+      </EuiFlexItem>
+    </EuiFlexGroup>
+  );
+};
diff --git a/x-pack/plugins/log_explorer/public/components/dataset_selector/sub_components/list_status.tsx b/x-pack/plugins/log_explorer/public/components/dataset_selector/sub_components/list_status.tsx
index 739475ea65230..3ea52197207ca 100644
--- a/x-pack/plugins/log_explorer/public/components/dataset_selector/sub_components/list_status.tsx
+++ b/x-pack/plugins/log_explorer/public/components/dataset_selector/sub_components/list_status.tsx
@@ -8,13 +8,14 @@
 import React from 'react';
 import { EuiButton, EuiEmptyPrompt, EuiText, EuiToolTip } from '@elastic/eui';
 import { FormattedMessage } from '@kbn/i18n-react';
+import type { DataViewListItem } from '@kbn/data-views-plugin/common';
 import { ReloadDatasets } from '../../../hooks/use_datasets';
 import { errorLabel, noDataRetryLabel } from '../constants';
 import type { Dataset, Integration } from '../../../../common/datasets';
 import { DatasetSkeleton } from './datasets_skeleton';
 
 export interface ListStatusProps {
-  data: Dataset[] | Integration[] | null;
+  data: Dataset[] | Integration[] | DataViewListItem[] | null;
   description: string;
   error: Error | null;
   isLoading: boolean;
diff --git a/x-pack/plugins/log_explorer/public/components/dataset_selector/types.ts b/x-pack/plugins/log_explorer/public/components/dataset_selector/types.ts
index 61542c7ccb702..f704791b004ff 100644
--- a/x-pack/plugins/log_explorer/public/components/dataset_selector/types.ts
+++ b/x-pack/plugins/log_explorer/public/components/dataset_selector/types.ts
@@ -6,7 +6,12 @@
  */
 
 import { EuiContextMenuPanelId } from '@elastic/eui/src/components/context_menu/context_menu';
-import type { DatasetSelection, DatasetSelectionChange } from '../../../common/dataset_selection';
+import { DataViewListItem } from '@kbn/data-views-plugin/common';
+import type {
+  DatasetSelection,
+  DatasetSelectionChange,
+  DataViewSelection,
+} from '../../../common/dataset_selection';
 import { SortOrder } from '../../../common/latest';
 import { Dataset, Integration, IntegrationId } from '../../../common/datasets';
 import { LoadDatasets, ReloadDatasets, SearchDatasets } from '../../hooks/use_datasets';
@@ -15,7 +20,13 @@ import {
   ReloadIntegrations,
   SearchIntegrations,
 } from '../../hooks/use_integrations';
-import { INTEGRATIONS_PANEL_ID, INTEGRATIONS_TAB_ID, UNCATEGORIZED_TAB_ID } from './constants';
+import {
+  DATA_VIEWS_TAB_ID,
+  INTEGRATIONS_PANEL_ID,
+  INTEGRATIONS_TAB_ID,
+  UNCATEGORIZED_TAB_ID,
+} from './constants';
+import { LoadDataViews, ReloadDataViews, SearchDataViews } from '../../hooks/use_data_views';
 
 export interface DatasetSelectorProps {
   /* The generic data stream list */
@@ -24,36 +35,52 @@ export interface DatasetSelectorProps {
   datasetsError: Error | null;
   /* The current selection instance */
   datasetSelection: DatasetSelection;
+  /* The available data views list */
+  dataViews: DataViewListItem[] | null;
+  /* Any error occurred to show when the user preview the data views */
+  dataViewsError: Error | null;
   /* The integrations list, each integration includes its data streams */
   integrations: Integration[] | null;
   /* Any error occurred to show when the user preview the integrations */
   integrationsError: Error | null;
-  /* Flags for loading/searching integrations or data streams*/
+  /* Flags for loading/searching integrations, data streams or data views*/
+  isLoadingDataViews: boolean;
   isLoadingIntegrations: boolean;
-  isLoadingStreams: boolean;
+  isLoadingUncategorized: boolean;
   isSearchingIntegrations: boolean;
+  /* Triggered when retrying to load the data views */
+  onDataViewsReload: ReloadDataViews;
+  /* Triggered when selecting a data view */
+  onDataViewSelection: DataViewSelection;
+  /* Triggered when the data views tab is selected */
+  onDataViewsTabClick: LoadDataViews;
   /* Triggered when we reach the bottom of the integration list and want to load more */
   onIntegrationsLoadMore: LoadMoreIntegrations;
   /* Triggered when the user reload the list after an error */
   onIntegrationsReload: ReloadIntegrations;
   /* Triggered when a search or sorting is performed */
+  onDataViewsSearch: SearchDataViews;
+  onDataViewsSort: SearchDataViews;
   onIntegrationsSearch: SearchIntegrations;
   onIntegrationsSort: SearchIntegrations;
   onIntegrationsStreamsSearch: SearchIntegrations;
   onIntegrationsStreamsSort: SearchIntegrations;
-  onUnmanagedStreamsSearch: SearchDatasets;
-  onUnmanagedStreamsSort: SearchDatasets;
+  onUncategorizedSearch: SearchDatasets;
+  onUncategorizedSort: SearchDatasets;
   /* Triggered when retrying to load the data streams */
-  onUnmanagedStreamsReload: ReloadDatasets;
-  /* Triggered when the uncategorized streams entry is selected */
-  onStreamsEntryClick: LoadDatasets;
+  onUncategorizedReload: ReloadDatasets;
+  /* Triggered when the uncategorized tab is selected */
+  onUncategorizedTabClick: LoadDatasets;
   /* Triggered when the selection is updated */
   onSelectionChange: DatasetSelectionChange;
 }
 
 export type PanelId = typeof INTEGRATIONS_PANEL_ID | IntegrationId;
 
-export type TabId = typeof INTEGRATIONS_TAB_ID | typeof UNCATEGORIZED_TAB_ID;
+export type TabId =
+  | typeof INTEGRATIONS_TAB_ID
+  | typeof UNCATEGORIZED_TAB_ID
+  | typeof DATA_VIEWS_TAB_ID;
 
 export interface SearchParams {
   integrationId?: PanelId;
@@ -68,3 +95,5 @@ export type DatasetsSelectorSearchHandler = (params: DatasetsSelectorSearchParam
 export type ChangePanelHandler = ({ panelId }: { panelId: EuiContextMenuPanelId }) => void;
 
 export type DatasetSelectionHandler = (dataset: Dataset) => void;
+
+export type DataViewSelectionHandler = (dataView: DataViewListItem) => void;
diff --git a/x-pack/plugins/log_explorer/public/components/dataset_selector/utils.tsx b/x-pack/plugins/log_explorer/public/components/dataset_selector/utils.tsx
index 283a204d8c759..91681a91569eb 100644
--- a/x-pack/plugins/log_explorer/public/components/dataset_selector/utils.tsx
+++ b/x-pack/plugins/log_explorer/public/components/dataset_selector/utils.tsx
@@ -13,6 +13,8 @@ import {
   DATA_VIEW_POPOVER_CONTENT_WIDTH,
   noDatasetsDescriptionLabel,
   noDatasetsLabel,
+  noDataViewsDescriptionLabel,
+  noDataViewsLabel,
   noIntegrationsDescriptionLabel,
   noIntegrationsLabel,
 } from './constants';
@@ -116,3 +118,19 @@ export const createUncategorizedStatusItem = (
     ),
   };
 };
+
+export const createDataViewsStatusItem = (
+  props: Omit<ListStatusProps, 'description' | 'title'>
+) => {
+  return {
+    disabled: true,
+    name: (
+      <ListStatus
+        key="dataViewsStatusItem"
+        description={noDataViewsDescriptionLabel}
+        title={noDataViewsLabel}
+        {...props}
+      />
+    ),
+  };
+};
diff --git a/x-pack/plugins/log_explorer/public/components/log_explorer/log_explorer.tsx b/x-pack/plugins/log_explorer/public/components/log_explorer/log_explorer.tsx
index e3fd7fbd4746e..0612713085224 100644
--- a/x-pack/plugins/log_explorer/public/components/log_explorer/log_explorer.tsx
+++ b/x-pack/plugins/log_explorer/public/components/log_explorer/log_explorer.tsx
@@ -8,19 +8,19 @@
 import React, { useMemo } from 'react';
 import { ScopedHistory } from '@kbn/core-application-browser';
 import { DataPublicPluginStart } from '@kbn/data-plugin/public';
-import { DiscoverAppState, DiscoverStart } from '@kbn/discover-plugin/public';
+import { DiscoverAppState } from '@kbn/discover-plugin/public';
 import type { BehaviorSubject } from 'rxjs';
+import { CoreStart } from '@kbn/core/public';
 import { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
 import { HIDE_ANNOUNCEMENTS } from '@kbn/discover-utils';
-import {
-  createLogExplorerProfileCustomizations,
-  CreateLogExplorerProfileCustomizationsDeps,
-} from '../../customizations/log_explorer_profile';
+import { createLogExplorerProfileCustomizations } from '../../customizations/log_explorer_profile';
 import { createPropertyGetProxy } from '../../utils/proxies';
 import { LogExplorerProfileContext } from '../../state_machines/log_explorer_profile';
+import { LogExplorerStartDeps } from '../../types';
 
-export interface CreateLogExplorerArgs extends CreateLogExplorerProfileCustomizationsDeps {
-  discover: DiscoverStart;
+export interface CreateLogExplorerArgs {
+  core: CoreStart;
+  plugins: LogExplorerStartDeps;
 }
 
 export interface LogExplorerStateContainer {
@@ -33,11 +33,12 @@ export interface LogExplorerProps {
   state$?: BehaviorSubject<LogExplorerStateContainer>;
 }
 
-export const createLogExplorer = ({
-  core,
-  data,
-  discover: { DiscoverContainer },
-}: CreateLogExplorerArgs) => {
+export const createLogExplorer = ({ core, plugins }: CreateLogExplorerArgs) => {
+  const {
+    data,
+    discover: { DiscoverContainer },
+  } = plugins;
+
   const overrideServices = {
     data: createDataServiceProxy(data),
     uiSettings: createUiSettingsServiceProxy(core.uiSettings),
@@ -45,7 +46,7 @@ export const createLogExplorer = ({
 
   return ({ scopedHistory, state$ }: LogExplorerProps) => {
     const logExplorerCustomizations = useMemo(
-      () => [createLogExplorerProfileCustomizations({ core, data, state$ })],
+      () => [createLogExplorerProfileCustomizations({ core, plugins, state$ })],
       [state$]
     );
 
diff --git a/x-pack/plugins/log_explorer/public/customizations/custom_dataset_selector.tsx b/x-pack/plugins/log_explorer/public/customizations/custom_dataset_selector.tsx
index aab114d350031..81aff790be9df 100644
--- a/x-pack/plugins/log_explorer/public/customizations/custom_dataset_selector.tsx
+++ b/x-pack/plugins/log_explorer/public/customizations/custom_dataset_selector.tsx
@@ -6,12 +6,15 @@
  */
 
 import React from 'react';
+import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
+import { DiscoverStart } from '@kbn/discover-plugin/public';
 import { DatasetSelector } from '../components/dataset_selector';
 import { DatasetsProvider, useDatasetsContext } from '../hooks/use_datasets';
 import { IntegrationsProvider, useIntegrationsContext } from '../hooks/use_integrations';
 import { IDatasetsClient } from '../services/datasets';
 import { LogExplorerProfileStateService } from '../state_machines/log_explorer_profile';
 import { useDatasetSelection } from '../hooks/use_dataset_selection';
+import { DataViewsProvider, useDataViewsContext } from '../hooks/use_data_views';
 
 interface CustomDatasetSelectorProps {
   logExplorerProfileStateService: LogExplorerProfileStateService;
@@ -38,23 +41,42 @@ export const CustomDatasetSelector = withProviders(({ logExplorerProfileStateSer
   const {
     datasets,
     error: datasetsError,
-    isLoading: isLoadingStreams,
+    isLoading: isLoadingUncategorized,
     loadDatasets,
     reloadDatasets,
     searchDatasets,
     sortDatasets,
   } = useDatasetsContext();
 
+  const {
+    dataViews,
+    error: dataViewsError,
+    isLoading: isLoadingDataViews,
+    loadDataViews,
+    reloadDataViews,
+    selectDataView,
+    searchDataViews,
+    sortDataViews,
+  } = useDataViewsContext();
+
   return (
     <DatasetSelector
       datasets={datasets}
       datasetSelection={datasetSelection}
       datasetsError={datasetsError}
+      dataViews={dataViews}
+      dataViewsError={dataViewsError}
       integrations={integrations}
       integrationsError={integrationsError}
+      isLoadingDataViews={isLoadingDataViews}
       isLoadingIntegrations={isLoadingIntegrations}
-      isLoadingStreams={isLoadingStreams}
+      isLoadingUncategorized={isLoadingUncategorized}
       isSearchingIntegrations={isSearchingIntegrations}
+      onDataViewsReload={reloadDataViews}
+      onDataViewsSearch={searchDataViews}
+      onDataViewSelection={selectDataView}
+      onDataViewsSort={sortDataViews}
+      onDataViewsTabClick={loadDataViews}
       onIntegrationsLoadMore={loadMore}
       onIntegrationsReload={reloadIntegrations}
       onIntegrationsSearch={searchIntegrations}
@@ -62,10 +84,10 @@ export const CustomDatasetSelector = withProviders(({ logExplorerProfileStateSer
       onIntegrationsStreamsSearch={searchIntegrationsStreams}
       onIntegrationsStreamsSort={sortIntegrationsStreams}
       onSelectionChange={handleDatasetSelectionChange}
-      onStreamsEntryClick={loadDatasets}
-      onUnmanagedStreamsReload={reloadDatasets}
-      onUnmanagedStreamsSearch={searchDatasets}
-      onUnmanagedStreamsSort={sortDatasets}
+      onUncategorizedReload={reloadDatasets}
+      onUncategorizedSearch={searchDatasets}
+      onUncategorizedSort={sortDatasets}
+      onUncategorizedTabClick={loadDatasets}
     />
   );
 });
@@ -75,17 +97,23 @@ export default CustomDatasetSelector;
 
 export type CustomDatasetSelectorBuilderProps = CustomDatasetSelectorProps & {
   datasetsClient: IDatasetsClient;
+  dataViews: DataViewsPublicPluginStart;
+  discover: DiscoverStart;
 };
 
 function withProviders(Component: React.FunctionComponent<CustomDatasetSelectorProps>) {
   return function ComponentWithProviders({
-    logExplorerProfileStateService,
     datasetsClient,
+    dataViews,
+    discover,
+    logExplorerProfileStateService,
   }: CustomDatasetSelectorBuilderProps) {
     return (
       <IntegrationsProvider datasetsClient={datasetsClient}>
         <DatasetsProvider datasetsClient={datasetsClient}>
-          <Component logExplorerProfileStateService={logExplorerProfileStateService} />
+          <DataViewsProvider dataViewsService={dataViews} discoverService={discover}>
+            <Component logExplorerProfileStateService={logExplorerProfileStateService} />
+          </DataViewsProvider>
         </DatasetsProvider>
       </IntegrationsProvider>
     );
diff --git a/x-pack/plugins/log_explorer/public/customizations/log_explorer_profile.tsx b/x-pack/plugins/log_explorer/public/customizations/log_explorer_profile.tsx
index c5d98b9b24101..ad120b3cc4d76 100644
--- a/x-pack/plugins/log_explorer/public/customizations/log_explorer_profile.tsx
+++ b/x-pack/plugins/log_explorer/public/customizations/log_explorer_profile.tsx
@@ -4,8 +4,6 @@
  * 2.0; you may not use this file except in compliance with the Elastic License
  * 2.0.
  */
-
-import { DataPublicPluginStart } from '@kbn/data-plugin/public';
 import type { CoreStart } from '@kbn/core/public';
 import { CustomizationCallback, DiscoverStateContainer } from '@kbn/discover-plugin/public';
 import React from 'react';
@@ -13,19 +11,21 @@ import { type BehaviorSubject, combineLatest, from, map, Subscription } from 'rx
 import { dynamic } from '../utils/dynamic';
 import { LogExplorerProfileStateService } from '../state_machines/log_explorer_profile';
 import { LogExplorerStateContainer } from '../components/log_explorer';
+import { LogExplorerStartDeps } from '../types';
 
 const LazyCustomDatasetSelector = dynamic(() => import('./custom_dataset_selector'));
 const LazyCustomDatasetFilters = dynamic(() => import('./custom_dataset_filters'));
 
 export interface CreateLogExplorerProfileCustomizationsDeps {
   core: CoreStart;
-  data: DataPublicPluginStart;
+  plugins: LogExplorerStartDeps;
   state$?: BehaviorSubject<LogExplorerStateContainer>;
 }
 
 export const createLogExplorerProfileCustomizations =
-  ({ core, data, state$ }: CreateLogExplorerProfileCustomizationsDeps): CustomizationCallback =>
+  ({ core, plugins, state$ }: CreateLogExplorerProfileCustomizationsDeps): CustomizationCallback =>
   async ({ customizations, stateContainer }) => {
+    const { data, dataViews, discover } = plugins;
     // Lazy load dependencies
     const datasetServiceModuleLoadable = import('../services/datasets');
     const logExplorerMachineModuleLoadable = import('../state_machines/log_explorer_profile');
@@ -72,6 +72,8 @@ export const createLogExplorerProfileCustomizations =
       CustomDataViewPicker: () => (
         <LazyCustomDatasetSelector
           datasetsClient={datasetsClient}
+          dataViews={dataViews}
+          discover={discover}
           logExplorerProfileStateService={logExplorerProfileStateService}
         />
       ),
diff --git a/x-pack/plugins/log_explorer/public/hooks/use_data_views.tsx b/x-pack/plugins/log_explorer/public/hooks/use_data_views.tsx
new file mode 100644
index 0000000000000..c6283aca4b7f9
--- /dev/null
+++ b/x-pack/plugins/log_explorer/public/hooks/use_data_views.tsx
@@ -0,0 +1,104 @@
+/*
+ * Copyright 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 createContainer from 'constate';
+import { useInterpret, useSelector } from '@xstate/react';
+import { DataViewListItem, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
+import { DiscoverStart } from '@kbn/discover-plugin/public';
+import { SortOrder } from '../../common/latest';
+import { createDataViewsStateMachine } from '../state_machines/data_views';
+
+interface DataViewsContextDeps {
+  dataViewsService: DataViewsPublicPluginStart;
+  discoverService: DiscoverStart;
+}
+
+export interface SearchDataViewsParams {
+  name: string;
+  sortOrder: SortOrder;
+}
+
+export type DataViewSelectionHandler = (dataView: DataViewListItem) => void;
+export type SearchDataViews = (params: SearchDataViewsParams) => void;
+export type LoadDataViews = () => void;
+export type ReloadDataViews = () => void;
+
+const useDataViews = ({ dataViewsService, discoverService }: DataViewsContextDeps) => {
+  const dataViewsStateService = useInterpret(() =>
+    createDataViewsStateMachine({
+      dataViews: dataViewsService,
+      discover: discoverService,
+    })
+  );
+
+  const dataViews = useSelector(dataViewsStateService, (state) => state.context.dataViews);
+
+  const error = useSelector(dataViewsStateService, (state) => state.context.error);
+
+  const isLoading = useSelector(dataViewsStateService, (state) => state.matches('loading'));
+
+  const loadDataViews = useCallback(
+    () => dataViewsStateService.send({ type: 'LOAD_DATA_VIEWS' }),
+    [dataViewsStateService]
+  );
+
+  const reloadDataViews = useCallback(
+    () => dataViewsStateService.send({ type: 'RELOAD_DATA_VIEWS' }),
+    [dataViewsStateService]
+  );
+
+  const searchDataViews: SearchDataViews = useCallback(
+    (searchParams) =>
+      dataViewsStateService.send({
+        type: 'SEARCH_DATA_VIEWS',
+        search: searchParams,
+      }),
+    [dataViewsStateService]
+  );
+
+  const selectDataView: DataViewSelectionHandler = useCallback(
+    (dataView) =>
+      dataViewsStateService.send({
+        type: 'SELECT_DATA_VIEW',
+        dataView,
+      }),
+    [dataViewsStateService]
+  );
+
+  const sortDataViews: SearchDataViews = useCallback(
+    (searchParams) =>
+      dataViewsStateService.send({
+        type: 'SORT_DATA_VIEWS',
+        search: searchParams,
+      }),
+    [dataViewsStateService]
+  );
+
+  return {
+    // Underlying state machine
+    dataViewsStateService,
+
+    // Failure states
+    error,
+
+    // Loading states
+    isLoading,
+
+    // Data
+    dataViews,
+
+    // Actions
+    loadDataViews,
+    reloadDataViews,
+    searchDataViews,
+    selectDataView,
+    sortDataViews,
+  };
+};
+
+export const [DataViewsProvider, useDataViewsContext] = createContainer(useDataViews);
diff --git a/x-pack/plugins/log_explorer/public/plugin.ts b/x-pack/plugins/log_explorer/public/plugin.ts
index 57df6b8e1ef0e..8375664c424f4 100644
--- a/x-pack/plugins/log_explorer/public/plugin.ts
+++ b/x-pack/plugins/log_explorer/public/plugin.ts
@@ -40,12 +40,9 @@ export class LogExplorerPlugin implements Plugin<LogExplorerPluginSetup, LogExpl
   }
 
   public start(core: CoreStart, plugins: LogExplorerStartDeps) {
-    const { data, discover } = plugins;
-
     const LogExplorer = createLogExplorer({
       core,
-      data,
-      discover,
+      plugins,
     });
 
     return {
diff --git a/x-pack/plugins/log_explorer/public/state_machines/data_views/index.ts b/x-pack/plugins/log_explorer/public/state_machines/data_views/index.ts
new file mode 100644
index 0000000000000..3b2a320ae181f
--- /dev/null
+++ b/x-pack/plugins/log_explorer/public/state_machines/data_views/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './src';
diff --git a/x-pack/plugins/log_explorer/public/state_machines/data_views/src/defaults.ts b/x-pack/plugins/log_explorer/public/state_machines/data_views/src/defaults.ts
new file mode 100644
index 0000000000000..8bc46876bde9b
--- /dev/null
+++ b/x-pack/plugins/log_explorer/public/state_machines/data_views/src/defaults.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 { HashedCache } from '../../../../common/hashed_cache';
+import { DefaultDataViewsContext } from './types';
+
+export const DEFAULT_CONTEXT: DefaultDataViewsContext = {
+  cache: new HashedCache(),
+  dataViewsSource: null,
+  dataViews: null,
+  error: null,
+  search: {
+    name: '',
+    sortOrder: 'asc',
+  },
+};
diff --git a/x-pack/plugins/log_explorer/public/state_machines/data_views/src/index.ts b/x-pack/plugins/log_explorer/public/state_machines/data_views/src/index.ts
new file mode 100644
index 0000000000000..86a51fb2c0e16
--- /dev/null
+++ b/x-pack/plugins/log_explorer/public/state_machines/data_views/src/index.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './state_machine';
+export * from './types';
diff --git a/x-pack/plugins/log_explorer/public/state_machines/data_views/src/state_machine.ts b/x-pack/plugins/log_explorer/public/state_machines/data_views/src/state_machine.ts
new file mode 100644
index 0000000000000..9f0aa9af396f3
--- /dev/null
+++ b/x-pack/plugins/log_explorer/public/state_machines/data_views/src/state_machine.ts
@@ -0,0 +1,160 @@
+/*
+ * Copyright 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 { DataViewListItem, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
+import { isError } from 'lodash';
+import { assign, createMachine } from 'xstate';
+import { DiscoverStart } from '@kbn/discover-plugin/public';
+import { parseDataViewListItem } from '../../../utils/parse_data_view_list_item';
+import { createComparatorByField } from '../../../utils/comparator_by_field';
+import { DEFAULT_CONTEXT } from './defaults';
+import type {
+  DataViewsContext,
+  DataViewsEvent,
+  DataViewsSearchParams,
+  DataViewsTypestate,
+  DefaultDataViewsContext,
+} from './types';
+
+export const createPureDataViewsStateMachine = (
+  initialContext: DefaultDataViewsContext = DEFAULT_CONTEXT
+) =>
+  createMachine<DataViewsContext, DataViewsEvent, DataViewsTypestate>(
+    {
+      /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVA1AlmA7rAHQCuAdjhejqgDY4BekAxADIDyAgsgPrKcAVTjywBJAKIB1AMoBtAAwBdRKAAOAe1g5q6sipAAPRABYAzKaIB2a6bOWArABoQAT0QBaAJzyi9y8YAmAEYADgcAX3DnNExcAmJadVQISihmCF0wIkoAN3UAayyY7DxCIkTk1IRc9QBjDBxdBUVm-Q0tHT0kQxNjSyJPP3lTIICnV0RLTyJ5eUsQ0ftI6IwS+PKklLI0sAAnXfVdolVaDAAzQ4BbImK4soqtqGqyPPrO5tbu9u1GrtAjBAANgcRFMDmcbgQIPsIQCw0WyxAt1KCU2kGyEFoYGY0nEnAASgBhAASfEEwjEUjkSjamh+un0AOsgN8nksgPsgLGEJMHKIgNCgRC9mMC2siOR6we6JwmOx0nY+IEZKEIgkMk+ajpnUZJk8ISIAQCHK540hpgCBoWgOM8lNEtWd1RyRlcpx4lY4kJyv4qspkk1IG+Ou6AMB8gNfkCoXBEwQ9gtM1GgzZ+qC4qiSMdKI2LogRAgYAARupyLUwNIwKhdrUABapSWEd0Ekkqinq6nKL7a366hBBeT2ez8zncuMJ6ZzEX2VNiywO2I56X5wslssVqs1+vbRuwZgGWCYdBZVBnY+7AAUplmAEpmLvc4WV8XS2Ry5Xq3WG9n4oHg73Q0mEIDXDYwwLMexLUBMEeQQcwLE8II0wCYxARCcMpwXNZ7k2VIADFUBwLEIGYfEPS4XhfXbKk-x7BlAKBaDfACSxbDBM0PACUFPGMBMEURMh1ELeBul3WkOgA-5EFMYUrBsOwOIQdwUK4oYRjGLCnVICgqBoegmAgcT6T+HoEDAlkxnmRZYPcIIhxmSx4Q0zMHweVIjJDKSzNtIgQlmKyx0hZThxCHi+OclZFylNFDO7CT6K83ifCNE1AsQFCglBIIbTtCKsyinC8wxLEPMk0zARtUF7Ds01YN44dAj8OFglFBTNKXGKCxfdcPy3b8CpErV4pMgEBztUEVJjRSzEyzxLOaoJWvY9rosqbYCKIyBSoS0y5q4nLarjZT+j8BZnMiIA */
+      context: initialContext,
+      preserveActionOrder: true,
+      predictableActionArguments: true,
+      id: 'DataViews',
+      initial: 'uninitialized',
+      states: {
+        uninitialized: {
+          on: {
+            LOAD_DATA_VIEWS: 'loading',
+          },
+        },
+        loading: {
+          id: 'loading',
+          invoke: {
+            src: 'loadDataViews',
+            onDone: {
+              target: 'loaded',
+              actions: ['storeInCache', 'storeDataViews', 'storeSearch'],
+            },
+            onError: 'loadingFailed',
+          },
+        },
+        loaded: {
+          id: 'loaded',
+          initial: 'idle',
+          states: {
+            idle: {
+              on: {
+                SEARCH_DATA_VIEWS: 'debounceSearchingDataViews',
+                SORT_DATA_VIEWS: {
+                  actions: ['storeSearch', 'searchDataViews'],
+                },
+                SELECT_DATA_VIEW: {
+                  actions: ['navigateToDiscoverDataView'],
+                },
+              },
+            },
+            debounceSearchingDataViews: {
+              entry: 'storeSearch',
+              on: {
+                SEARCH_DATA_VIEWS: 'debounceSearchingDataViews',
+              },
+              after: {
+                300: {
+                  target: 'idle',
+                  actions: 'searchDataViews',
+                },
+              },
+            },
+          },
+        },
+        loadingFailed: {
+          entry: ['clearCache', 'clearData', 'storeError'],
+          exit: 'clearError',
+          on: {
+            RELOAD_DATA_VIEWS: 'loading',
+          },
+        },
+      },
+    },
+    {
+      actions: {
+        storeSearch: assign((_context, event) => ({
+          // Store search from search event
+          ...('search' in event && { search: event.search }),
+        })),
+        storeDataViews: assign((_context, event) =>
+          'data' in event && !isError(event.data)
+            ? { dataViewsSource: event.data, dataViews: event.data }
+            : {}
+        ),
+        searchDataViews: assign((context) => {
+          if (context.dataViewsSource !== null) {
+            return {
+              dataViews: searchDataViews(context.dataViewsSource, context.search),
+            };
+          }
+          return {};
+        }),
+        storeInCache: (context, event) => {
+          if ('data' in event && !isError(event.data)) {
+            context.cache.set(context.search, event.data);
+          }
+        },
+        storeError: assign((_context, event) =>
+          'data' in event && isError(event.data) ? { error: event.data } : {}
+        ),
+        clearCache: (context) => {
+          context.cache.reset();
+        },
+        clearData: assign((_context) => ({ dataViews: null })),
+        clearError: assign((_context) => ({ error: null })),
+      },
+    }
+  );
+
+export interface DataViewsStateMachineDependencies {
+  initialContext?: DefaultDataViewsContext;
+  dataViews: DataViewsPublicPluginStart;
+  discover: DiscoverStart;
+}
+
+export const createDataViewsStateMachine = ({
+  initialContext,
+  dataViews,
+  discover,
+}: DataViewsStateMachineDependencies) =>
+  createPureDataViewsStateMachine(initialContext).withConfig({
+    actions: {
+      navigateToDiscoverDataView: (_context, event) => {
+        if (event.type === 'SELECT_DATA_VIEW' && 'dataView' in event) {
+          discover.locator?.navigate({ dataViewId: event.dataView.id });
+        }
+      },
+    },
+    services: {
+      loadDataViews: (context) => {
+        const searchParams = context.search;
+        return context.cache.has(searchParams)
+          ? Promise.resolve(context.cache.get(searchParams))
+          : dataViews.getIdsWithTitle().then((views) => views.map(parseDataViewListItem));
+      },
+    },
+  });
+
+const searchDataViews = (dataViews: DataViewListItem[], search: DataViewsSearchParams) => {
+  const { name, sortOrder } = search;
+
+  return dataViews
+    .filter((dataView) => Boolean(dataView.name?.includes(name ?? '')))
+    .sort(createComparatorByField<DataViewListItem>('name', sortOrder));
+};
diff --git a/x-pack/plugins/log_explorer/public/state_machines/data_views/src/types.ts b/x-pack/plugins/log_explorer/public/state_machines/data_views/src/types.ts
new file mode 100644
index 0000000000000..a2f369133521c
--- /dev/null
+++ b/x-pack/plugins/log_explorer/public/state_machines/data_views/src/types.ts
@@ -0,0 +1,101 @@
+/*
+ * Copyright 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 { DoneInvokeEvent } from 'xstate';
+import type { DataViewListItem } from '@kbn/data-views-plugin/common';
+import type { IHashedCache } from '../../../../common/hashed_cache';
+import { SortOrder } from '../../../../common/latest';
+
+export interface DataViewsSearchParams {
+  name?: string;
+  sortOrder?: SortOrder;
+}
+
+export interface WithCache {
+  cache: IHashedCache<DataViewsSearchParams, DataViewListItem[]>;
+}
+
+export interface WithSearch {
+  search: DataViewsSearchParams;
+}
+
+export interface WithDataViews {
+  dataViewsSource: DataViewListItem[];
+  dataViews: DataViewListItem[];
+}
+
+export interface WithNullishDataViews {
+  dataViewsSource: null;
+  dataViews: null;
+}
+
+export interface WithError {
+  error: Error;
+}
+
+export interface WithNullishError {
+  error: null;
+}
+
+export type DefaultDataViewsContext = WithCache &
+  WithNullishDataViews &
+  WithSearch &
+  WithNullishError;
+
+type LoadingDataViewsContext = DefaultDataViewsContext;
+
+type LoadedDataViewsContext = WithCache & WithDataViews & WithSearch & WithNullishError;
+
+type LoadingFailedDataViewsContext = WithCache & WithNullishDataViews & WithSearch & WithError;
+
+export type DataViewsTypestate =
+  | {
+      value: 'uninitialized';
+      context: DefaultDataViewsContext;
+    }
+  | {
+      value: 'loading';
+      context: LoadingDataViewsContext;
+    }
+  | {
+      value: 'loaded';
+      context: LoadedDataViewsContext;
+    }
+  | {
+      value: 'loaded.idle';
+      context: LoadedDataViewsContext;
+    }
+  | {
+      value: 'loaded.debounceSearchingDataViews';
+      context: LoadedDataViewsContext;
+    }
+  | {
+      value: 'loadingFailed';
+      context: LoadingFailedDataViewsContext;
+    };
+
+export type DataViewsContext = DataViewsTypestate['context'];
+
+export type DataViewsEvent =
+  | {
+      type: 'LOAD_DATA_VIEWS';
+    }
+  | {
+      type: 'RELOAD_DATA_VIEWS';
+    }
+  | {
+      type: 'SELECT_DATA_VIEW';
+      dataView: DataViewListItem;
+    }
+  | {
+      type: 'SEARCH_DATA_VIEWS';
+      search: DataViewsSearchParams;
+    }
+  | {
+      type: 'SORT_DATA_VIEWS';
+      search: DataViewsSearchParams;
+    }
+  | DoneInvokeEvent<DataViewListItem[] | Error>;
diff --git a/x-pack/plugins/log_explorer/public/types.ts b/x-pack/plugins/log_explorer/public/types.ts
index 07a345bc661d2..b260f6ba68ad7 100644
--- a/x-pack/plugins/log_explorer/public/types.ts
+++ b/x-pack/plugins/log_explorer/public/types.ts
@@ -4,10 +4,11 @@
  * 2.0; you may not use this file except in compliance with the Elastic License
  * 2.0.
  */
+import type { ComponentType } from 'react';
 import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
+import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
 import type { DiscoverSetup, DiscoverStart } from '@kbn/discover-plugin/public';
 import { SharePluginSetup } from '@kbn/share-plugin/public';
-import type { ComponentType } from 'react';
 import { LogExplorerLocators } from '../common/locators';
 import type { LogExplorerProps } from './components/log_explorer';
 
@@ -25,5 +26,6 @@ export interface LogExplorerSetupDeps {
 
 export interface LogExplorerStartDeps {
   data: DataPublicPluginStart;
+  dataViews: DataViewsPublicPluginStart;
   discover: DiscoverStart;
 }
diff --git a/x-pack/plugins/log_explorer/public/utils/get_data_view_test_subj.ts b/x-pack/plugins/log_explorer/public/utils/get_data_view_test_subj.ts
new file mode 100644
index 0000000000000..8cf915b64691c
--- /dev/null
+++ b/x-pack/plugins/log_explorer/public/utils/get_data_view_test_subj.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.
+ */
+
+const BASE_DATA_VIEW_TEST_SUBJ = 'logExplorerDataView';
+
+const publicDataViewPatternsSet = new Set(['logs-*', 'logstash-*', 'filebeat-*']);
+
+export const getDataViewTestSubj = (title: string) => {
+  if (publicDataViewPatternsSet.has(title)) {
+    return [BASE_DATA_VIEW_TEST_SUBJ, cleanTitle(title)].join('_');
+  }
+
+  return BASE_DATA_VIEW_TEST_SUBJ;
+};
+
+const cleanTitle = (title: string) => title.slice(0, -2);
diff --git a/x-pack/plugins/log_explorer/public/utils/parse_data_view_list_item.ts b/x-pack/plugins/log_explorer/public/utils/parse_data_view_list_item.ts
new file mode 100644
index 0000000000000..ecf509c0945d9
--- /dev/null
+++ b/x-pack/plugins/log_explorer/public/utils/parse_data_view_list_item.ts
@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { DataViewListItem } from '@kbn/data-views-plugin/common';
+
+export const parseDataViewListItem = (dataViewListItem: DataViewListItem) => {
+  return {
+    ...dataViewListItem,
+    name: dataViewListItem.name ?? dataViewListItem.title,
+  };
+};
diff --git a/x-pack/plugins/observability_log_explorer/common/translations.ts b/x-pack/plugins/observability_log_explorer/common/translations.ts
index 8974c8a3f449e..c7a63e4f9866e 100644
--- a/x-pack/plugins/observability_log_explorer/common/translations.ts
+++ b/x-pack/plugins/observability_log_explorer/common/translations.ts
@@ -25,7 +25,7 @@ export const betaBadgeDescription = i18n.translate(
 export const discoverLinkTitle = i18n.translate(
   'xpack.observabilityLogExplorer.discoverLinkTitle',
   {
-    defaultMessage: 'Discover',
+    defaultMessage: 'Open in Discover',
   }
 );
 
diff --git a/x-pack/test/functional/apps/observability_log_explorer/dataset_selector.ts b/x-pack/test/functional/apps/observability_log_explorer/dataset_selector.ts
index dc8ce8b67c786..d7d63384eec29 100644
--- a/x-pack/test/functional/apps/observability_log_explorer/dataset_selector.ts
+++ b/x-pack/test/functional/apps/observability_log_explorer/dataset_selector.ts
@@ -14,23 +14,32 @@ const initialPackageMap = {
 };
 const initialPackagesTexts = Object.values(initialPackageMap);
 
+const expectedDataViews = ['logstash-*', 'logs-*', 'metrics-*'];
+const sortedExpectedDataViews = expectedDataViews.slice().sort();
+
 const uncategorized = ['logs-gaming-*', 'logs-manufacturing-*', 'logs-retail-*'];
 const expectedUncategorized = uncategorized.map((dataset) => dataset.split('-')[1]);
 
 export default function ({ getService, getPageObjects }: FtrProviderContext) {
   const browser = getService('browser');
   const esArchiver = getService('esArchiver');
+  const kibanaServer = getService('kibanaServer');
   const retry = getService('retry');
-  const PageObjects = getPageObjects(['common', 'observabilityLogExplorer']);
+  const PageObjects = getPageObjects(['common', 'discover', 'observabilityLogExplorer']);
 
   const noIntegrationsTitle = 'No integrations found';
   const noUncategorizedTitle = 'No data streams found';
 
   describe('Dataset Selector', () => {
     before(async () => {
+      await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover');
       await PageObjects.observabilityLogExplorer.removeInstalledPackages();
     });
 
+    after(async () => {
+      await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover');
+    });
+
     describe('as consistent behavior', () => {
       before(async () => {
         await PageObjects.observabilityLogExplorer.navigateTo();
@@ -41,14 +50,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
         await PageObjects.observabilityLogExplorer.openDatasetSelector();
       });
 
-      it('should always display the Integrations and Uncategorized top level tabs', async () => {
+      it('should always display the Integrations Uncategorized and Data Views top level tabs', async () => {
         const integrationsTab = await PageObjects.observabilityLogExplorer.getIntegrationsTab();
         const uncategorizedTab = await PageObjects.observabilityLogExplorer.getUncategorizedTab();
+        const dataViewsTab = await PageObjects.observabilityLogExplorer.getDataViewsTab();
 
         expect(await integrationsTab.isDisplayed()).to.be(true);
         expect(await integrationsTab.getVisibleText()).to.be('Integrations');
         expect(await uncategorizedTab.isDisplayed()).to.be(true);
         expect(await uncategorizedTab.getVisibleText()).to.be('Uncategorized');
+        expect(await dataViewsTab.isDisplayed()).to.be(true);
+        expect(await dataViewsTab.getVisibleText()).to.be('Data Views');
       });
 
       it('should always display the "Show all logs" action', async () => {
@@ -565,6 +577,143 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
         });
       });
 
+      describe('when open on the data views tab', () => {
+        before(async () => {
+          await PageObjects.observabilityLogExplorer.navigateTo();
+        });
+
+        beforeEach(async () => {
+          await browser.refresh();
+          await PageObjects.observabilityLogExplorer.openDatasetSelector();
+          await PageObjects.observabilityLogExplorer.getDataViewsTab().then((tab) => tab.click());
+        });
+
+        it('should display a list of available data views', async () => {
+          await retry.try(async () => {
+            const [panelTitleNode, menuEntries] = await PageObjects.observabilityLogExplorer
+              .getDataViewsContextMenu()
+              .then((menu) =>
+                Promise.all([
+                  PageObjects.observabilityLogExplorer.getPanelTitle(menu),
+                  PageObjects.observabilityLogExplorer.getPanelEntries(menu),
+                ])
+              );
+
+            expect(
+              await PageObjects.observabilityLogExplorer.getDataViewsContextMenuTitle(
+                panelTitleNode
+              )
+            ).to.be('Data Views');
+            expect(await menuEntries[0].getVisibleText()).to.be(expectedDataViews[0]);
+            expect(await menuEntries[1].getVisibleText()).to.be(expectedDataViews[1]);
+            expect(await menuEntries[2].getVisibleText()).to.be(expectedDataViews[2]);
+          });
+        });
+
+        it('should sort the data views list by the clicked sorting option', async () => {
+          await retry.try(async () => {
+            const panelTitleNode = await PageObjects.observabilityLogExplorer
+              .getDataViewsContextMenu()
+              .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu));
+
+            expect(
+              await PageObjects.observabilityLogExplorer.getDataViewsContextMenuTitle(
+                panelTitleNode
+              )
+            ).to.be('Data Views');
+          });
+
+          // Test descending order
+          await PageObjects.observabilityLogExplorer.clickSortButtonBy('desc');
+          await retry.try(async () => {
+            const menuEntries = await PageObjects.observabilityLogExplorer
+              .getDataViewsContextMenu()
+              .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu));
+
+            expect(await menuEntries[0].getVisibleText()).to.be(sortedExpectedDataViews[2]);
+            expect(await menuEntries[1].getVisibleText()).to.be(sortedExpectedDataViews[1]);
+            expect(await menuEntries[2].getVisibleText()).to.be(sortedExpectedDataViews[0]);
+          });
+
+          // Test back ascending order
+          await PageObjects.observabilityLogExplorer.clickSortButtonBy('asc');
+          await retry.try(async () => {
+            const menuEntries = await PageObjects.observabilityLogExplorer
+              .getDataViewsContextMenu()
+              .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu));
+
+            expect(await menuEntries[0].getVisibleText()).to.be(sortedExpectedDataViews[0]);
+            expect(await menuEntries[1].getVisibleText()).to.be(sortedExpectedDataViews[1]);
+            expect(await menuEntries[2].getVisibleText()).to.be(sortedExpectedDataViews[2]);
+          });
+        });
+
+        it('should filter the datasets list by the typed data view name', async () => {
+          await retry.try(async () => {
+            const panelTitleNode = await PageObjects.observabilityLogExplorer
+              .getDataViewsContextMenu()
+              .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu));
+
+            expect(
+              await PageObjects.observabilityLogExplorer.getDataViewsContextMenuTitle(
+                panelTitleNode
+              )
+            ).to.be('Data Views');
+          });
+
+          await retry.try(async () => {
+            const menuEntries = await PageObjects.observabilityLogExplorer
+              .getDataViewsContextMenu()
+              .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu));
+
+            expect(await menuEntries[0].getVisibleText()).to.be(expectedDataViews[0]);
+            expect(await menuEntries[1].getVisibleText()).to.be(expectedDataViews[1]);
+            expect(await menuEntries[2].getVisibleText()).to.be(expectedDataViews[2]);
+          });
+
+          await PageObjects.observabilityLogExplorer.typeSearchFieldWith('logs');
+
+          await retry.try(async () => {
+            const menuEntries = await PageObjects.observabilityLogExplorer
+              .getDataViewsContextMenu()
+              .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu));
+
+            expect(menuEntries.length).to.be(2);
+            expect(await menuEntries[0].getVisibleText()).to.be('logs-*');
+            expect(await menuEntries[1].getVisibleText()).to.be('logstash-*');
+          });
+        });
+
+        it('should navigate to Discover with the clicked data view preselected', async () => {
+          await retry.try(async () => {
+            const panelTitleNode = await PageObjects.observabilityLogExplorer
+              .getDataViewsContextMenu()
+              .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu));
+
+            expect(
+              await PageObjects.observabilityLogExplorer.getDataViewsContextMenuTitle(
+                panelTitleNode
+              )
+            ).to.be('Data Views');
+          });
+
+          await retry.try(async () => {
+            const menuEntries = await PageObjects.observabilityLogExplorer
+              .getDataViewsContextMenu()
+              .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu));
+
+            expect(await menuEntries[2].getVisibleText()).to.be(expectedDataViews[2]);
+            menuEntries[2].click();
+          });
+
+          await retry.try(async () => {
+            expect(await PageObjects.discover.getCurrentlySelectedDataView()).to.eql(
+              expectedDataViews[2]
+            );
+          });
+        });
+      });
+
       describe('when open/close the selector', () => {
         before(async () => {
           await PageObjects.observabilityLogExplorer.navigateTo();
diff --git a/x-pack/test/functional/page_objects/observability_log_explorer.ts b/x-pack/test/functional/page_objects/observability_log_explorer.ts
index fe3837e46ec24..e4b8270cff6d4 100644
--- a/x-pack/test/functional/page_objects/observability_log_explorer.ts
+++ b/x-pack/test/functional/page_objects/observability_log_explorer.ts
@@ -208,6 +208,18 @@ export function ObservabilityLogExplorerPageObject({
       return testSubjects.find('datasetSelectorUncategorizedTab');
     },
 
+    getDataViewsContextMenu() {
+      return testSubjects.find('dataViewsContextMenu');
+    },
+
+    getDataViewsContextMenuTitle(panelTitleNode: WebElementWrapper) {
+      return panelTitleNode.getVisibleText().then((title) => title.split('\n')[0]);
+    },
+
+    getDataViewsTab() {
+      return testSubjects.find('datasetSelectorDataViewsTab');
+    },
+
     getPanelTitle(contextMenu: WebElementWrapper) {
       return contextMenu.findByClassName('euiContextMenuPanelTitle');
     },
diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/dataset_selector.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/dataset_selector.ts
index 5947c5d7d35cf..d53f76435559f 100644
--- a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/dataset_selector.ts
+++ b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/dataset_selector.ts
@@ -14,6 +14,9 @@ const initialPackageMap = {
 };
 const initialPackagesTexts = Object.values(initialPackageMap);
 
+const expectedDataViews = ['logs-*', 'metrics-*'];
+const sortedExpectedDataViews = expectedDataViews.slice().sort();
+
 const uncategorized = ['logs-gaming-*', 'logs-manufacturing-*', 'logs-retail-*'];
 const expectedUncategorized = uncategorized.map((dataset) => dataset.split('-')[1]);
 
@@ -21,7 +24,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
   const browser = getService('browser');
   const esArchiver = getService('esArchiver');
   const retry = getService('retry');
-  const PageObjects = getPageObjects(['common', 'observabilityLogExplorer', 'svlCommonPage']);
+  const PageObjects = getPageObjects([
+    'common',
+    'discover',
+    'observabilityLogExplorer',
+    'svlCommonPage',
+  ]);
 
   const noIntegrationsTitle = 'No integrations found';
   const noUncategorizedTitle = 'No data streams found';
@@ -46,14 +54,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
         await PageObjects.observabilityLogExplorer.openDatasetSelector();
       });
 
-      it('should always display the Integrations and Uncategorized top level tabs', async () => {
+      it('should always display the Integrations Uncategorized and Data Views top level tabs', async () => {
         const integrationsTab = await PageObjects.observabilityLogExplorer.getIntegrationsTab();
         const uncategorizedTab = await PageObjects.observabilityLogExplorer.getUncategorizedTab();
+        const dataViewsTab = await PageObjects.observabilityLogExplorer.getDataViewsTab();
 
         expect(await integrationsTab.isDisplayed()).to.be(true);
         expect(await integrationsTab.getVisibleText()).to.be('Integrations');
         expect(await uncategorizedTab.isDisplayed()).to.be(true);
         expect(await uncategorizedTab.getVisibleText()).to.be('Uncategorized');
+        expect(await dataViewsTab.isDisplayed()).to.be(true);
+        expect(await dataViewsTab.getVisibleText()).to.be('Data Views');
       });
 
       it('should always display the "Show all logs" action', async () => {
@@ -570,6 +581,138 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
         });
       });
 
+      describe('when open on the data views tab', () => {
+        before(async () => {
+          await PageObjects.observabilityLogExplorer.navigateTo();
+        });
+
+        beforeEach(async () => {
+          await browser.refresh();
+          await PageObjects.observabilityLogExplorer.openDatasetSelector();
+          await PageObjects.observabilityLogExplorer.getDataViewsTab().then((tab) => tab.click());
+        });
+
+        it('should display a list of available data views', async () => {
+          await retry.try(async () => {
+            const [panelTitleNode, menuEntries] = await PageObjects.observabilityLogExplorer
+              .getDataViewsContextMenu()
+              .then((menu) =>
+                Promise.all([
+                  PageObjects.observabilityLogExplorer.getPanelTitle(menu),
+                  PageObjects.observabilityLogExplorer.getPanelEntries(menu),
+                ])
+              );
+
+            expect(
+              await PageObjects.observabilityLogExplorer.getDataViewsContextMenuTitle(
+                panelTitleNode
+              )
+            ).to.be('Data Views');
+            expect(await menuEntries[0].getVisibleText()).to.be(expectedDataViews[0]);
+            expect(await menuEntries[1].getVisibleText()).to.be(expectedDataViews[1]);
+          });
+        });
+
+        it('should sort the data views list by the clicked sorting option', async () => {
+          await retry.try(async () => {
+            const panelTitleNode = await PageObjects.observabilityLogExplorer
+              .getDataViewsContextMenu()
+              .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu));
+
+            expect(
+              await PageObjects.observabilityLogExplorer.getDataViewsContextMenuTitle(
+                panelTitleNode
+              )
+            ).to.be('Data Views');
+          });
+
+          // Test descending order
+          await PageObjects.observabilityLogExplorer.clickSortButtonBy('desc');
+          await retry.try(async () => {
+            const menuEntries = await PageObjects.observabilityLogExplorer
+              .getDataViewsContextMenu()
+              .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu));
+
+            expect(await menuEntries[0].getVisibleText()).to.be(sortedExpectedDataViews[1]);
+            expect(await menuEntries[1].getVisibleText()).to.be(sortedExpectedDataViews[0]);
+          });
+
+          // Test back ascending order
+          await PageObjects.observabilityLogExplorer.clickSortButtonBy('asc');
+          await retry.try(async () => {
+            const menuEntries = await PageObjects.observabilityLogExplorer
+              .getDataViewsContextMenu()
+              .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu));
+
+            expect(await menuEntries[0].getVisibleText()).to.be(sortedExpectedDataViews[0]);
+            expect(await menuEntries[1].getVisibleText()).to.be(sortedExpectedDataViews[1]);
+          });
+        });
+
+        it('should filter the datasets list by the typed data view name', async () => {
+          await retry.try(async () => {
+            const panelTitleNode = await PageObjects.observabilityLogExplorer
+              .getDataViewsContextMenu()
+              .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu));
+
+            expect(
+              await PageObjects.observabilityLogExplorer.getDataViewsContextMenuTitle(
+                panelTitleNode
+              )
+            ).to.be('Data Views');
+          });
+
+          await retry.try(async () => {
+            const menuEntries = await PageObjects.observabilityLogExplorer
+              .getDataViewsContextMenu()
+              .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu));
+
+            expect(await menuEntries[0].getVisibleText()).to.be(expectedDataViews[0]);
+            expect(await menuEntries[1].getVisibleText()).to.be(expectedDataViews[1]);
+          });
+
+          await PageObjects.observabilityLogExplorer.typeSearchFieldWith('logs');
+
+          await retry.try(async () => {
+            const menuEntries = await PageObjects.observabilityLogExplorer
+              .getDataViewsContextMenu()
+              .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu));
+
+            expect(menuEntries.length).to.be(1);
+            expect(await menuEntries[0].getVisibleText()).to.be('logs-*');
+          });
+        });
+
+        it('should navigate to Discover with the clicked data view preselected', async () => {
+          await retry.try(async () => {
+            const panelTitleNode = await PageObjects.observabilityLogExplorer
+              .getDataViewsContextMenu()
+              .then((menu) => PageObjects.observabilityLogExplorer.getPanelTitle(menu));
+
+            expect(
+              await PageObjects.observabilityLogExplorer.getDataViewsContextMenuTitle(
+                panelTitleNode
+              )
+            ).to.be('Data Views');
+          });
+
+          await retry.try(async () => {
+            const menuEntries = await PageObjects.observabilityLogExplorer
+              .getDataViewsContextMenu()
+              .then((menu) => PageObjects.observabilityLogExplorer.getPanelEntries(menu));
+
+            expect(await menuEntries[1].getVisibleText()).to.be(expectedDataViews[1]);
+            menuEntries[1].click();
+          });
+
+          await retry.try(async () => {
+            expect(await PageObjects.discover.getCurrentlySelectedDataView()).to.eql(
+              expectedDataViews[1]
+            );
+          });
+        });
+      });
+
       describe('when open/close the selector', () => {
         before(async () => {
           await PageObjects.observabilityLogExplorer.navigateTo();

From d79a38363a0820153a283b776dd2ac557032eaa8 Mon Sep 17 00:00:00 2001
From: Carlos Crespo <crespocarlos@users.noreply.github.com>
Date: Thu, 28 Sep 2023 12:39:04 +0200
Subject: [PATCH 07/20] [Infra UI] Cleanup host-specific Node Detail Page code 
 (#166828)

closes: https://github.com/elastic/kibana/issues/166428

## Summary

This PR removes code that is no longer needed after replacing the Node
Details View for Host with the Asset Details.

### TSVB

The TSVB files were apparently only used to display charts in the Node
Details view. Due to the Asset Details using Lens to power the charts,
corresponding `host` TSVB formulas and configs are no longer needed.

Therefore, `host*`, `hostK8s*`, and `hostDocker*` (the latter appears to
have never been used) have been removed. Additionally, `aws*` from
`required_metrics` was also removed because it was host-specific.

### FE Components

The main change is in the `useMetadata` hook. I have changed the hook
signature, making `requiredMetrics` optional. This parameter is used to
process additional filtering and is only used in asset types that the
old Node Details page supports. Not passing it is not expected to break
other places that depend on this hook.

### Server

Removing TSVB files has a direct impact on the
`api/metrics/node_details` endpoint. This endpoint is only used to
provide data to the Node Details page. It returns a 400 error if an
invalid metric is passed - which will be the case for hosts

**Example Request:**
```json
POST kbn:api/metrics/node_details
{
  "metrics": [
    "hostK8sCpuCap",
    "hostSystemOverview"
  ],
  "nodeId": "gke-release-oblt-release-oblt-pool-c4163099-bcaj",
  "nodeType": "host",
  "timerange": {
    "from": 1695210522045,
    "to": 1695214122045,
    "interval": ">=1m"
  },
  "cloudId": "6106013995483209805",
  "sourceId": "default"
}
```

**Response:**
```json
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "Failed to validate: \n  in metrics/0: \"hostK8sCpuCap\" does not match expected type \"podOverview\" | \"podCpuUsage\" | \"podMemoryUsage\" | \"podLogUsage\" | \"podNetworkTraffic\" | \"containerOverview\" | \"containerCpuKernel\" | \"containerCpuUsage\" | \"containerDiskIOOps\" | \"containerDiskIOBytes\" | \"containerMemory\" | \"containerNetworkTraffic\" | \"containerK8sOverview\" | \"containerK8sCpuUsage\" | \"containerK8sMemoryUsage\" | \"nginxHits\" | \"nginxRequestRate\" | \"nginxActiveConnections\" | \"nginxRequestsPerConnection\" | \"awsEC2CpuUtilization\" | \"awsEC2NetworkTraffic\" | \"awsEC2DiskIOBytes\" | \"awsS3TotalRequests\" | \"awsS3NumberOfObjects\" | \"awsS3BucketSize\" | \"awsS3DownloadBytes\" | \"awsS3UploadBytes\" | \"awsRDSCpuTotal\" | \"awsRDSConnections\" | \"awsRDSQueriesExecuted\" | \"awsRDSActiveTransactions\" | \"awsRDSLatency\" | \"awsSQSMessagesVisible\" | \"awsSQSMessagesDelayed\" | \"awsSQSMessagesSent\" | \"awsSQSMessagesEmpty\" | \"awsSQSOldestMessage\" | \"custom\"\n  in metrics/1: \"hostSystemOverview\" does not match expected type \"podOverview\" | \"podCpuUsage\" | \"podMemoryUsage\" | \"podLogUsage\" | \"podNetworkTraffic\" | \"containerOverview\" | \"containerCpuKernel\" | \"containerCpuUsage\" | \"containerDiskIOOps\" | \"containerDiskIOBytes\" | \"containerMemory\" | \"containerNetworkTraffic\" | \"containerK8sOverview\" | \"containerK8sCpuUsage\" | \"containerK8sMemoryUsage\" | \"nginxHits\" | \"nginxRequestRate\" | \"nginxActiveConnections\" | \"nginxRequestsPerConnection\" | \"awsEC2CpuUtilization\" | \"awsEC2NetworkTraffic\" | \"awsEC2DiskIOBytes\" | \"awsS3TotalRequests\" | \"awsS3NumberOfObjects\" | \"awsS3BucketSize\" | \"awsS3DownloadBytes\" | \"awsS3UploadBytes\" | \"awsRDSCpuTotal\" | \"awsRDSConnections\" | \"awsRDSQueriesExecuted\" | \"awsRDSActiveTransactions\" | \"awsRDSLatency\" | \"awsSQSMessagesVisible\" | \"awsSQSMessagesDelayed\" | \"awsSQSMessagesSent\" | \"awsSQSMessagesEmpty\" | \"awsSQSOldestMessage\" | \"custom\""
}
```

### How to Test
- Start a local Kibana instance pointing to an oblt cluster.
- Navigate to `Infrastructure`.
- Try different asset types and navigate to the Node Details view.

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
 .../common/inventory_models/host/index.ts     |  18 -
 .../inventory_models/host/metrics/index.ts    |  35 --
 .../host/metrics/tsvb/host_cpu_usage.ts       | 254 ------------
 .../host/metrics/tsvb/host_docker_info.ts     |  56 ---
 .../host/metrics/tsvb/host_docker_overview.ts |  67 ----
 .../metrics/tsvb/host_docker_top_5_by_cpu.ts  |  37 --
 .../tsvb/host_docker_top_5_by_memory.ts       |  37 --
 .../host/metrics/tsvb/host_filesystem.ts      |  38 --
 .../host/metrics/tsvb/host_k8s_cpu_cap.ts     |  58 ---
 .../host/metrics/tsvb/host_k8s_disk_cap.ts    |  45 ---
 .../host/metrics/tsvb/host_k8s_memory_cap.ts  |  46 ---
 .../host/metrics/tsvb/host_k8s_overview.ts    | 155 --------
 .../host/metrics/tsvb/host_k8s_pod_cap.ts     |  47 ---
 .../host/metrics/tsvb/host_load.ts            |  56 ---
 .../host/metrics/tsvb/host_memory_usage.ts    |  78 ----
 .../host/metrics/tsvb/host_network_traffic.ts | 109 -----
 .../host/metrics/tsvb/host_system_overview.ts | 130 ------
 .../inventory_models/shared/metrics/index.ts  |  12 -
 .../shared/metrics/required_metrics.ts        |   9 -
 .../metrics/tsvb/aws_cpu_utilization.ts       |  35 --
 .../shared/metrics/tsvb/aws_diskio_bytes.ts   |  84 ----
 .../shared/metrics/tsvb/aws_diskio_ops.ts     |  83 ----
 .../shared/metrics/tsvb/aws_network_bytes.ts  |  86 ----
 .../metrics/tsvb/aws_network_packets.ts       |  52 ---
 .../shared/metrics/tsvb/aws_overview.ts       |  65 ---
 .../infra/common/inventory_models/types.ts    |  25 +-
 .../asset_details/hooks/use_metadata.ts       |  37 +-
 .../asset_details/hooks/use_metadata_state.ts |  11 +-
 .../node_details/tabs/osquery/index.tsx       |  13 +-
 .../node_details/tabs/properties/index.tsx    |  13 +-
 .../metric_detail/components/layout.tsx       |   5 +-
 .../layouts/aws_layout_sections.tsx           | 242 -----------
 .../components/layouts/host_layout.tsx        | 376 ------------------
 .../metric_detail/metric_detail_page.tsx      |   8 +-
 .../infra/server/routes/node_details/index.ts |  64 +--
 .../translations/translations/fr-FR.json      |  47 +--
 .../translations/translations/ja-JP.json      |  47 +--
 .../translations/translations/zh-CN.json      |  47 +--
 .../apis/metrics_ui/metrics.ts                |  40 +-
 39 files changed, 107 insertions(+), 2560 deletions(-)
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_cpu_usage.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_info.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_overview.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_top_5_by_cpu.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_top_5_by_memory.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_filesystem.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_cpu_cap.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_disk_cap.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_memory_cap.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_overview.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_pod_cap.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_load.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_memory_usage.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_network_traffic.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_system_overview.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_cpu_utilization.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_diskio_bytes.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_diskio_ops.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_network_bytes.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_network_packets.ts
 delete mode 100644 x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_overview.ts
 delete mode 100644 x-pack/plugins/infra/public/pages/metrics/metric_detail/components/layouts/aws_layout_sections.tsx
 delete mode 100644 x-pack/plugins/infra/public/pages/metrics/metric_detail/components/layouts/host_layout.tsx

diff --git a/x-pack/plugins/infra/common/inventory_models/host/index.ts b/x-pack/plugins/infra/common/inventory_models/host/index.ts
index 04aa00fbdf2be..18af8bbfadc99 100644
--- a/x-pack/plugins/infra/common/inventory_models/host/index.ts
+++ b/x-pack/plugins/infra/common/inventory_models/host/index.ts
@@ -8,10 +8,6 @@
 import { i18n } from '@kbn/i18n';
 import { metrics } from './metrics';
 import { InventoryModel } from '../types';
-import {
-  aws as awsRequiredMetrics,
-  nginx as nginxRequireMetrics,
-} from '../shared/metrics/required_metrics';
 
 export { hostSnapshotMetricTypes } from './metrics';
 
@@ -38,19 +34,5 @@ export const host: InventoryModel = {
     cloudProvider: 'cloud.provider',
   },
   metrics,
-  requiredMetrics: [
-    'hostSystemOverview',
-    'hostCpuUsage',
-    'hostLoad',
-    'hostMemoryUsage',
-    'hostNetworkTraffic',
-    'hostK8sOverview',
-    'hostK8sCpuCap',
-    'hostK8sMemoryCap',
-    'hostK8sDiskCap',
-    'hostK8sPodCap',
-    ...awsRequiredMetrics,
-    ...nginxRequireMetrics,
-  ],
   tooltipMetrics: ['cpu', 'memory', 'tx', 'rx'],
 };
diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/index.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/index.ts
index e59aaefb2b82b..3eff64ccb6a7a 100644
--- a/x-pack/plugins/infra/common/inventory_models/host/metrics/index.ts
+++ b/x-pack/plugins/infra/common/inventory_models/host/metrics/index.ts
@@ -18,24 +18,6 @@ import { normalizedLoad1m } from './snapshot/normalized_load_1m';
 import { rx } from './snapshot/rx';
 import { tx } from './snapshot/tx';
 
-import { hostSystemOverview } from './tsvb/host_system_overview';
-import { hostCpuUsage } from './tsvb/host_cpu_usage';
-import { hostLoad } from './tsvb/host_load';
-import { hostMemoryUsage } from './tsvb/host_memory_usage';
-import { hostNetworkTraffic } from './tsvb/host_network_traffic';
-import { hostFilesystem } from './tsvb/host_filesystem';
-
-import { hostK8sOverview } from './tsvb/host_k8s_overview';
-import { hostK8sCpuCap } from './tsvb/host_k8s_cpu_cap';
-import { hostK8sPodCap } from './tsvb/host_k8s_pod_cap';
-import { hostK8sDiskCap } from './tsvb/host_k8s_disk_cap';
-import { hostK8sMemoryCap } from './tsvb/host_k8s_memory_cap';
-
-import { hostDockerTop5ByMemory } from './tsvb/host_docker_top_5_by_memory';
-import { hostDockerTop5ByCpu } from './tsvb/host_docker_top_5_by_cpu';
-import { hostDockerOverview } from './tsvb/host_docker_overview';
-import { hostDockerInfo } from './tsvb/host_docker_info';
-
 import { InventoryMetrics } from '../../types';
 
 const exposedHostSnapshotMetrics = {
@@ -59,23 +41,6 @@ export const hostSnapshotMetricTypes = Object.keys(exposedHostSnapshotMetrics) a
 >;
 
 export const metrics: InventoryMetrics = {
-  tsvb: {
-    hostSystemOverview,
-    hostCpuUsage,
-    hostLoad,
-    hostMemoryUsage,
-    hostNetworkTraffic,
-    hostFilesystem,
-    hostK8sOverview,
-    hostK8sCpuCap,
-    hostK8sPodCap,
-    hostK8sDiskCap,
-    hostK8sMemoryCap,
-    hostDockerOverview,
-    hostDockerInfo,
-    hostDockerTop5ByMemory,
-    hostDockerTop5ByCpu,
-  },
   snapshot: hostSnapshotMetrics,
   defaultSnapshot: 'cpu',
   defaultTimeRangeInSeconds: 3600, // 1 hour
diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_cpu_usage.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_cpu_usage.ts
deleted file mode 100644
index bcafeb4ebc4cf..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_cpu_usage.ts
+++ /dev/null
@@ -1,254 +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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostCpuUsage: TSVBMetricModelCreator = (
-  timeField,
-  indexPattern,
-  interval
-): TSVBMetricModel => ({
-  id: 'hostCpuUsage',
-  requires: ['system.cpu'],
-  index_pattern: indexPattern,
-  interval,
-  time_field: timeField,
-  type: 'timeseries',
-  series: [
-    {
-      id: 'user',
-      metrics: [
-        {
-          field: 'system.cpu.user.pct',
-          id: 'avg-cpu-user',
-          type: 'avg',
-        },
-        {
-          field: 'system.cpu.cores',
-          id: 'max-cpu-cores',
-          type: 'max',
-        },
-        {
-          id: 'calc-avg-cores',
-          script: 'params.avg / params.cores',
-          type: 'calculation',
-          variables: [
-            {
-              field: 'max-cpu-cores',
-              id: 'var-cores',
-              name: 'cores',
-            },
-            {
-              field: 'avg-cpu-user',
-              id: 'var-avg',
-              name: 'avg',
-            },
-          ],
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'system',
-      metrics: [
-        {
-          field: 'system.cpu.system.pct',
-          id: 'avg-cpu-system',
-          type: 'avg',
-        },
-        {
-          field: 'system.cpu.cores',
-          id: 'max-cpu-cores',
-          type: 'max',
-        },
-        {
-          id: 'calc-avg-cores',
-          script: 'params.avg / params.cores',
-          type: 'calculation',
-          variables: [
-            {
-              field: 'max-cpu-cores',
-              id: 'var-cores',
-              name: 'cores',
-            },
-            {
-              field: 'avg-cpu-system',
-              id: 'var-avg',
-              name: 'avg',
-            },
-          ],
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'steal',
-      metrics: [
-        {
-          field: 'system.cpu.steal.pct',
-          id: 'avg-cpu-steal',
-          type: 'avg',
-        },
-        {
-          field: 'system.cpu.cores',
-          id: 'max-cpu-cores',
-          type: 'max',
-        },
-        {
-          id: 'calc-avg-cores',
-          script: 'params.avg / params.cores',
-          type: 'calculation',
-          variables: [
-            {
-              field: 'avg-cpu-steal',
-              id: 'var-avg',
-              name: 'avg',
-            },
-            {
-              field: 'max-cpu-cores',
-              id: 'var-cores',
-              name: 'cores',
-            },
-          ],
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'irq',
-      metrics: [
-        {
-          field: 'system.cpu.irq.pct',
-          id: 'avg-cpu-irq',
-          type: 'avg',
-        },
-        {
-          field: 'system.cpu.cores',
-          id: 'max-cpu-cores',
-          type: 'max',
-        },
-        {
-          id: 'calc-avg-cores',
-          script: 'params.avg / params.cores',
-          type: 'calculation',
-          variables: [
-            {
-              field: 'max-cpu-cores',
-              id: 'var-cores',
-              name: 'cores',
-            },
-            {
-              field: 'avg-cpu-irq',
-              id: 'var-avg',
-              name: 'avg',
-            },
-          ],
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'softirq',
-      metrics: [
-        {
-          field: 'system.cpu.softirq.pct',
-          id: 'avg-cpu-softirq',
-          type: 'avg',
-        },
-        {
-          field: 'system.cpu.cores',
-          id: 'max-cpu-cores',
-          type: 'max',
-        },
-        {
-          id: 'calc-avg-cores',
-          script: 'params.avg / params.cores',
-          type: 'calculation',
-          variables: [
-            {
-              field: 'max-cpu-cores',
-              id: 'var-cores',
-              name: 'cores',
-            },
-            {
-              field: 'avg-cpu-softirq',
-              id: 'var-avg',
-              name: 'avg',
-            },
-          ],
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'iowait',
-      metrics: [
-        {
-          field: 'system.cpu.iowait.pct',
-          id: 'avg-cpu-iowait',
-          type: 'avg',
-        },
-        {
-          field: 'system.cpu.cores',
-          id: 'max-cpu-cores',
-          type: 'max',
-        },
-        {
-          id: 'calc-avg-cores',
-          script: 'params.avg / params.cores',
-          type: 'calculation',
-          variables: [
-            {
-              field: 'max-cpu-cores',
-              id: 'var-cores',
-              name: 'cores',
-            },
-            {
-              field: 'avg-cpu-iowait',
-              id: 'var-avg',
-              name: 'avg',
-            },
-          ],
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'nice',
-      metrics: [
-        {
-          field: 'system.cpu.nice.pct',
-          id: 'avg-cpu-nice',
-          type: 'avg',
-        },
-        {
-          field: 'system.cpu.cores',
-          id: 'max-cpu-cores',
-          type: 'max',
-        },
-        {
-          id: 'calc-avg-cores',
-          script: 'params.avg / params.cores',
-          type: 'calculation',
-          variables: [
-            {
-              field: 'max-cpu-cores',
-              id: 'var-cores',
-              name: 'cores',
-            },
-            {
-              field: 'avg-cpu-nice',
-              id: 'var-avg',
-              name: 'avg',
-            },
-          ],
-        },
-      ],
-      split_mode: 'everything',
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_info.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_info.ts
deleted file mode 100644
index 4cc2b574362d7..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_info.ts
+++ /dev/null
@@ -1,56 +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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostDockerInfo: TSVBMetricModelCreator = (
-  timeField,
-  indexPattern,
-  interval
-): TSVBMetricModel => ({
-  id: 'hostDockerInfo',
-  requires: ['docker.info'],
-  index_pattern: indexPattern,
-  interval,
-  time_field: timeField,
-  type: 'timeseries',
-  series: [
-    {
-      id: 'running',
-      metrics: [
-        {
-          field: 'docker.info.containers.running',
-          id: 'max-running',
-          type: 'max',
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'paused',
-      metrics: [
-        {
-          field: 'docker.info.containers.paused',
-          id: 'max-paused',
-          type: 'max',
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'stopped',
-      metrics: [
-        {
-          field: 'docker.info.containers.stopped',
-          id: 'max-stopped',
-          type: 'max',
-        },
-      ],
-      split_mode: 'everything',
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_overview.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_overview.ts
deleted file mode 100644
index df56a21dbf5b7..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_overview.ts
+++ /dev/null
@@ -1,67 +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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostDockerOverview: TSVBMetricModelCreator = (
-  timeField,
-  indexPattern,
-  interval
-): TSVBMetricModel => ({
-  id: 'hostDockerOverview',
-  requires: ['docker.info'],
-  index_pattern: indexPattern,
-  interval,
-  time_field: timeField,
-  type: 'top_n',
-  series: [
-    {
-      id: 'total',
-      metrics: [
-        {
-          field: 'docker.info.containers.total',
-          id: 'max-total',
-          type: 'max',
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'running',
-      metrics: [
-        {
-          field: 'docker.info.containers.running',
-          id: 'max-running',
-          type: 'max',
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'paused',
-      metrics: [
-        {
-          field: 'docker.info.containers.paused',
-          id: 'max-paused',
-          type: 'max',
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'stopped',
-      metrics: [
-        {
-          field: 'docker.info.containers.stopped',
-          id: 'max-stopped',
-          type: 'max',
-        },
-      ],
-      split_mode: 'everything',
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_top_5_by_cpu.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_top_5_by_cpu.ts
deleted file mode 100644
index fd9eb97419736..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_top_5_by_cpu.ts
+++ /dev/null
@@ -1,37 +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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostDockerTop5ByCpu: TSVBMetricModelCreator = (
-  timeField,
-  indexPattern,
-  interval
-): TSVBMetricModel => ({
-  id: 'hostDockerTop5ByCpu',
-  requires: ['docker.cpu'],
-  index_pattern: indexPattern,
-  interval,
-  time_field: timeField,
-  type: 'timeseries',
-  series: [
-    {
-      id: 'avg-cpu',
-      metrics: [
-        {
-          field: 'docker.cpu.total.pct',
-          id: 'avg-cpu-metric',
-          type: 'avg',
-        },
-      ],
-      split_mode: 'terms',
-      terms_field: 'container.name',
-      terms_order_by: 'avg-cpu',
-      terms_size: 5,
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_top_5_by_memory.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_top_5_by_memory.ts
deleted file mode 100644
index cad828671d232..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_top_5_by_memory.ts
+++ /dev/null
@@ -1,37 +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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostDockerTop5ByMemory: TSVBMetricModelCreator = (
-  timeField,
-  indexPattern,
-  interval
-): TSVBMetricModel => ({
-  id: 'hostDockerTop5ByMemory',
-  requires: ['docker.memory'],
-  index_pattern: indexPattern,
-  interval,
-  time_field: timeField,
-  type: 'timeseries',
-  series: [
-    {
-      id: 'avg-memory',
-      metrics: [
-        {
-          field: 'docker.memory.usage.pct',
-          id: 'avg-memory-metric',
-          type: 'avg',
-        },
-      ],
-      split_mode: 'terms',
-      terms_field: 'container.name',
-      terms_order_by: 'avg-memory',
-      terms_size: 5,
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_filesystem.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_filesystem.ts
deleted file mode 100644
index ce284345410fc..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_filesystem.ts
+++ /dev/null
@@ -1,38 +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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostFilesystem: TSVBMetricModelCreator = (
-  timeField,
-  indexPattern,
-  interval
-): TSVBMetricModel => ({
-  id: 'hostFilesystem',
-  requires: ['system.filesystem'],
-  filter: 'system.filesystem.device_name:\\/*',
-  index_pattern: indexPattern,
-  time_field: timeField,
-  interval,
-  type: 'timeseries',
-  series: [
-    {
-      id: 'used',
-      metrics: [
-        {
-          field: 'system.filesystem.used.pct',
-          id: 'avg-filesystem-used',
-          type: 'avg',
-        },
-      ],
-      split_mode: 'terms',
-      terms_field: 'system.filesystem.device_name',
-      terms_order_by: 'used',
-      terms_size: 5,
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_cpu_cap.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_cpu_cap.ts
deleted file mode 100644
index d33e4cdeb34cd..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_cpu_cap.ts
+++ /dev/null
@@ -1,58 +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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostK8sCpuCap: TSVBMetricModelCreator = (
-  timeField,
-  indexPattern,
-  interval
-): TSVBMetricModel => ({
-  id: 'hostK8sCpuCap',
-  map_field_to: 'kubernetes.node.name',
-  requires: ['kubernetes.node'],
-  index_pattern: indexPattern,
-  interval,
-  time_field: timeField,
-  type: 'timeseries',
-  series: [
-    {
-      id: 'capacity',
-      metrics: [
-        {
-          field: 'kubernetes.node.cpu.allocatable.cores',
-          id: 'max-cpu-cap',
-          type: 'max',
-        },
-        {
-          id: 'calc-nanocores',
-          type: 'calculation',
-          variables: [
-            {
-              id: 'var-cores',
-              field: 'max-cpu-cap',
-              name: 'cores',
-            },
-          ],
-          script: 'params.cores * 1000000000',
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'used',
-      metrics: [
-        {
-          field: 'kubernetes.node.cpu.usage.nanocores',
-          id: 'avg-cpu-usage',
-          type: 'avg',
-        },
-      ],
-      split_mode: 'everything',
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_disk_cap.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_disk_cap.ts
deleted file mode 100644
index e9e512a136631..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_disk_cap.ts
+++ /dev/null
@@ -1,45 +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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-export const hostK8sDiskCap: TSVBMetricModelCreator = (
-  timeField,
-  indexPattern,
-  interval
-): TSVBMetricModel => ({
-  id: 'hostK8sDiskCap',
-  map_field_to: 'kubernetes.node.name',
-  requires: ['kubernetes.node'],
-  index_pattern: indexPattern,
-  interval,
-  time_field: timeField,
-  type: 'timeseries',
-  series: [
-    {
-      id: 'capacity',
-      metrics: [
-        {
-          field: 'kubernetes.node.fs.capacity.bytes',
-          id: 'max-fs-cap',
-          type: 'max',
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'used',
-      metrics: [
-        {
-          field: 'kubernetes.node.fs.used.bytes',
-          id: 'avg-fs-used',
-          type: 'avg',
-        },
-      ],
-      split_mode: 'everything',
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_memory_cap.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_memory_cap.ts
deleted file mode 100644
index ccc227ef1854e..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_memory_cap.ts
+++ /dev/null
@@ -1,46 +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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostK8sMemoryCap: TSVBMetricModelCreator = (
-  timeField,
-  indexPattern,
-  interval
-): TSVBMetricModel => ({
-  id: 'hostK8sMemoryCap',
-  map_field_to: 'kubernetes.node.name',
-  requires: ['kubernetes.node'],
-  index_pattern: indexPattern,
-  interval,
-  time_field: timeField,
-  type: 'timeseries',
-  series: [
-    {
-      id: 'capacity',
-      metrics: [
-        {
-          field: 'kubernetes.node.memory.allocatable.bytes',
-          id: 'max-memory-cap',
-          type: 'max',
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'used',
-      metrics: [
-        {
-          field: 'kubernetes.node.memory.usage.bytes',
-          id: 'avg-memory-usage',
-          type: 'avg',
-        },
-      ],
-      split_mode: 'everything',
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_overview.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_overview.ts
deleted file mode 100644
index 2da74d50dff1b..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_overview.ts
+++ /dev/null
@@ -1,155 +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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostK8sOverview: TSVBMetricModelCreator = (
-  timeField,
-  indexPattern,
-  interval
-): TSVBMetricModel => ({
-  id: 'hostK8sOverview',
-  requires: ['kubernetes'],
-  index_pattern: indexPattern,
-  interval,
-  time_field: timeField,
-  type: 'top_n',
-  series: [
-    {
-      id: 'cpucap',
-      split_mode: 'everything',
-      metrics: [
-        {
-          field: 'kubernetes.node.cpu.allocatable.cores',
-          id: 'max-cpu-cap',
-          type: 'max',
-        },
-        {
-          field: 'kubernetes.node.cpu.usage.nanocores',
-          id: 'avg-cpu-usage',
-          type: 'avg',
-        },
-        {
-          id: 'calc-used-cap',
-          script: 'params.used / (params.cap * 1000000000)',
-          type: 'calculation',
-          variables: [
-            {
-              field: 'max-cpu-cap',
-              id: 'var-cap',
-              name: 'cap',
-            },
-            {
-              field: 'avg-cpu-usage',
-              id: 'var-used',
-              name: 'used',
-            },
-          ],
-        },
-      ],
-    },
-    {
-      id: 'diskcap',
-      metrics: [
-        {
-          field: 'kubernetes.node.fs.capacity.bytes',
-          id: 'max-fs-cap',
-          type: 'max',
-        },
-        {
-          field: 'kubernetes.node.fs.used.bytes',
-          id: 'avg-fs-used',
-          type: 'avg',
-        },
-        {
-          id: 'calc-used-cap',
-          script: 'params.used / params.cap',
-          type: 'calculation',
-          variables: [
-            {
-              field: 'max-fs-cap',
-              id: 'var-cap',
-              name: 'cap',
-            },
-            {
-              field: 'avg-fs-used',
-              id: 'var-used',
-              name: 'used',
-            },
-          ],
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'memorycap',
-      metrics: [
-        {
-          field: 'kubernetes.node.memory.allocatable.bytes',
-          id: 'max-memory-cap',
-          type: 'max',
-        },
-        {
-          field: 'kubernetes.node.memory.usage.bytes',
-          id: 'avg-memory-usage',
-          type: 'avg',
-        },
-        {
-          id: 'calc-used-cap',
-          script: 'params.used / params.cap',
-          type: 'calculation',
-          variables: [
-            {
-              field: 'max-memory-cap',
-              id: 'var-cap',
-              name: 'cap',
-            },
-            {
-              field: 'avg-memory-usage',
-              id: 'var-used',
-              name: 'used',
-            },
-          ],
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'podcap',
-      metrics: [
-        {
-          field: 'kubernetes.node.pod.capacity.total',
-          id: 'max-pod-cap',
-          type: 'max',
-        },
-        {
-          field: 'kubernetes.pod.uid',
-          id: 'card-pod-name',
-          type: 'cardinality',
-        },
-        {
-          id: 'calc-used-cap',
-          script: 'params.used / params.cap',
-          type: 'calculation',
-          variables: [
-            {
-              field: 'max-pod-cap',
-              id: 'var-cap',
-              name: 'cap',
-            },
-            {
-              field: 'card-pod-name',
-              id: 'var-used',
-              name: 'used',
-            },
-          ],
-        },
-      ],
-      split_mode: 'everything',
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_pod_cap.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_pod_cap.ts
deleted file mode 100644
index 85cb798eaf9b9..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_pod_cap.ts
+++ /dev/null
@@ -1,47 +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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostK8sPodCap: TSVBMetricModelCreator = (
-  timeField,
-  indexPattern,
-  interval
-): TSVBMetricModel => ({
-  id: 'hostK8sPodCap',
-  requires: ['kubernetes.node'],
-  map_field_to: 'kubernetes.node.name',
-  index_pattern: indexPattern,
-  interval,
-  time_field: timeField,
-  type: 'timeseries',
-
-  series: [
-    {
-      id: 'capacity',
-      metrics: [
-        {
-          field: 'kubernetes.node.pod.allocatable.total',
-          id: 'max-pod-cap',
-          type: 'max',
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'used',
-      metrics: [
-        {
-          field: 'kubernetes.pod.uid',
-          id: 'avg-pod',
-          type: 'cardinality',
-        },
-      ],
-      split_mode: 'everything',
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_load.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_load.ts
deleted file mode 100644
index bef170c743e6c..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_load.ts
+++ /dev/null
@@ -1,56 +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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostLoad: TSVBMetricModelCreator = (
-  timeField,
-  indexPattern,
-  interval
-): TSVBMetricModel => ({
-  id: 'hostLoad',
-  requires: ['system.cpu'],
-  index_pattern: indexPattern,
-  interval,
-  time_field: timeField,
-  type: 'timeseries',
-  series: [
-    {
-      id: 'load_1m',
-      metrics: [
-        {
-          field: 'system.load.1',
-          id: 'avg-load-1m',
-          type: 'avg',
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'load_5m',
-      metrics: [
-        {
-          field: 'system.load.5',
-          id: 'avg-load-5m',
-          type: 'avg',
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'load_15m',
-      metrics: [
-        {
-          field: 'system.load.15',
-          id: 'avg-load-15m',
-          type: 'avg',
-        },
-      ],
-      split_mode: 'everything',
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_memory_usage.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_memory_usage.ts
deleted file mode 100644
index bda81dea4bd28..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_memory_usage.ts
+++ /dev/null
@@ -1,78 +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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostMemoryUsage: TSVBMetricModelCreator = (
-  timeField,
-  indexPattern,
-  interval
-): TSVBMetricModel => ({
-  id: 'hostMemoryUsage',
-  requires: ['system.memory'],
-  index_pattern: indexPattern,
-  interval,
-  time_field: timeField,
-  type: 'timeseries',
-  series: [
-    {
-      id: 'free',
-      metrics: [
-        {
-          field: 'system.memory.free',
-          id: 'avg-memory-free',
-          type: 'avg',
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'used',
-      metrics: [
-        {
-          field: 'system.memory.actual.used.bytes',
-          id: 'avg-memory-used',
-          type: 'avg',
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'cache',
-      metrics: [
-        {
-          field: 'system.memory.actual.used.bytes',
-          id: 'avg-memory-actual-used',
-          type: 'avg',
-        },
-        {
-          field: 'system.memory.used.bytes',
-          id: 'avg-memory-used',
-          type: 'avg',
-        },
-        {
-          id: 'calc-used-actual',
-          script: 'params.used - params.actual',
-          type: 'calculation',
-          variables: [
-            {
-              field: 'avg-memory-actual-used',
-              id: 'var-actual',
-              name: 'actual',
-            },
-            {
-              field: 'avg-memory-used',
-              id: 'var-used',
-              name: 'used',
-            },
-          ],
-        },
-      ],
-      split_mode: 'everything',
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_network_traffic.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_network_traffic.ts
deleted file mode 100644
index b3dfc5e91ba0a..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_network_traffic.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostNetworkTraffic: TSVBMetricModelCreator = (
-  timeField,
-  indexPattern,
-  interval
-): TSVBMetricModel => ({
-  id: 'hostNetworkTraffic',
-  requires: ['system.network'],
-  index_pattern: indexPattern,
-  interval,
-  time_field: timeField,
-  type: 'timeseries',
-  series: [
-    {
-      id: 'tx',
-      metrics: [
-        {
-          field: 'host.network.egress.bytes',
-          id: 'avg-net-out',
-          type: 'avg',
-        },
-        {
-          id: 'max-period',
-          type: 'max',
-          field: 'metricset.period',
-        },
-        {
-          id: '3216b170-f192-11ec-a8e3-dd984b7213e2',
-          type: 'calculation',
-          variables: [
-            {
-              id: '34e64c30-f192-11ec-a8e3-dd984b7213e2',
-              name: 'value',
-              field: 'avg-net-out',
-            },
-            {
-              id: '3886cb80-f192-11ec-a8e3-dd984b7213e2',
-              name: 'period',
-              field: 'max-period',
-            },
-          ],
-          script: 'params.value / (params.period / 1000)',
-        },
-      ],
-      filter: {
-        language: 'kuery',
-        query: 'host.network.egress.bytes : * ',
-      },
-      split_mode: 'everything',
-    },
-    {
-      id: 'rx',
-      metrics: [
-        {
-          field: 'host.network.ingress.bytes',
-          id: 'avg-net-in',
-          type: 'avg',
-        },
-        {
-          id: 'calc-invert-rate',
-          script: 'params.rate * -1',
-          type: 'calculation',
-          variables: [
-            {
-              field: 'avg-net-in',
-              id: 'var-rate',
-              name: 'rate',
-            },
-          ],
-        },
-        {
-          id: 'max-period',
-          type: 'max',
-          field: 'metricset.period',
-        },
-        {
-          id: '3216b170-f192-11ec-a8e3-dd984b7213e2',
-          type: 'calculation',
-          variables: [
-            {
-              id: '34e64c30-f192-11ec-a8e3-dd984b7213e2',
-              name: 'value',
-              field: 'calc-invert-rate',
-            },
-            {
-              id: '3886cb80-f192-11ec-a8e3-dd984b7213e2',
-              name: 'period',
-              field: 'max-period',
-            },
-          ],
-          script: 'params.value / (params.period / 1000)',
-        },
-      ],
-      filter: {
-        language: 'kuery',
-        query: 'host.network.ingress.bytes : * ',
-      },
-      split_mode: 'everything',
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_system_overview.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_system_overview.ts
deleted file mode 100644
index 69ebd0aa35947..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_system_overview.ts
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const hostSystemOverview: TSVBMetricModelCreator = (
-  timeField,
-  indexPattern,
-  interval
-): TSVBMetricModel => ({
-  id: 'hostSystemOverview',
-  requires: ['system.cpu', 'system.memory', 'system.load', 'system.network'],
-  index_pattern: indexPattern,
-  interval,
-  time_field: timeField,
-  type: 'top_n',
-  series: [
-    {
-      id: 'cpu',
-      split_mode: 'everything',
-      metrics: [
-        {
-          field: 'system.cpu.total.norm.pct',
-          id: 'avg-cpu-total',
-          type: 'avg',
-        },
-      ],
-    },
-    {
-      id: 'load',
-      split_mode: 'everything',
-      metrics: [
-        {
-          field: 'system.load.5',
-          id: 'avg-load-5m',
-          type: 'avg',
-        },
-      ],
-    },
-    {
-      id: 'memory',
-      split_mode: 'everything',
-      metrics: [
-        {
-          field: 'system.memory.actual.used.pct',
-          id: 'avg-memory-actual-used',
-          type: 'avg',
-        },
-      ],
-    },
-    {
-      id: 'rx',
-      metrics: [
-        {
-          field: 'host.network.ingress.bytes',
-          id: 'avg-net-in',
-          type: 'avg',
-        },
-        {
-          id: 'max-period',
-          type: 'max',
-          field: 'metricset.period',
-        },
-        {
-          id: '3216b170-f192-11ec-a8e3-dd984b7213e2',
-          type: 'calculation',
-          variables: [
-            {
-              id: '34e64c30-f192-11ec-a8e3-dd984b7213e2',
-              name: 'value',
-              field: 'avg-net-in',
-            },
-            {
-              id: '3886cb80-f192-11ec-a8e3-dd984b7213e2',
-              name: 'period',
-              field: 'max-period',
-            },
-          ],
-          script: 'params.value / (params.period / 1000)',
-        },
-      ],
-      filter: {
-        language: 'kuery',
-        query: 'host.network.ingress.bytes : * ',
-      },
-      split_mode: 'everything',
-    },
-    {
-      id: 'tx',
-      metrics: [
-        {
-          field: 'host.network.egress.bytes',
-          id: 'avg-net-out',
-          type: 'avg',
-        },
-        {
-          id: 'max-period',
-          type: 'max',
-          field: 'metricset.period',
-        },
-        {
-          id: '3216b170-f192-11ec-a8e3-dd984b7213e2',
-          type: 'calculation',
-          variables: [
-            {
-              id: '34e64c30-f192-11ec-a8e3-dd984b7213e2',
-              name: 'value',
-              field: 'avg-net-out',
-            },
-            {
-              id: '3886cb80-f192-11ec-a8e3-dd984b7213e2',
-              name: 'period',
-              field: 'max-period',
-            },
-          ],
-          script: 'params.value / (params.period / 1000)',
-        },
-      ],
-      filter: {
-        language: 'kuery',
-        query: 'host.network.egress.bytes : * ',
-      },
-      split_mode: 'everything',
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/shared/metrics/index.ts b/x-pack/plugins/infra/common/inventory_models/shared/metrics/index.ts
index 775dff525b7f5..2dac80de51161 100644
--- a/x-pack/plugins/infra/common/inventory_models/shared/metrics/index.ts
+++ b/x-pack/plugins/infra/common/inventory_models/shared/metrics/index.ts
@@ -10,12 +10,6 @@ import { nginxActiveConnections } from './tsvb/nginx_active_connections';
 import { nginxHits } from './tsvb/nginx_hits';
 import { nginxRequestsPerConnection } from './tsvb/nginx_requests_per_connection';
 
-import { awsCpuUtilization } from './tsvb/aws_cpu_utilization';
-import { awsDiskioBytes } from './tsvb/aws_diskio_bytes';
-import { awsDiskioOps } from './tsvb/aws_diskio_ops';
-import { awsNetworkBytes } from './tsvb/aws_network_bytes';
-import { awsNetworkPackets } from './tsvb/aws_network_packets';
-import { awsOverview } from './tsvb/aws_overview';
 import { InventoryMetrics } from '../../types';
 import { count } from './snapshot/count';
 
@@ -25,12 +19,6 @@ export const metrics: InventoryMetrics = {
     nginxHits,
     nginxRequestRate,
     nginxRequestsPerConnection,
-    awsCpuUtilization,
-    awsDiskioBytes,
-    awsDiskioOps,
-    awsNetworkBytes,
-    awsNetworkPackets,
-    awsOverview,
   },
   snapshot: {
     count,
diff --git a/x-pack/plugins/infra/common/inventory_models/shared/metrics/required_metrics.ts b/x-pack/plugins/infra/common/inventory_models/shared/metrics/required_metrics.ts
index 5b29fc93e0d0c..172b48a1ba68d 100644
--- a/x-pack/plugins/infra/common/inventory_models/shared/metrics/required_metrics.ts
+++ b/x-pack/plugins/infra/common/inventory_models/shared/metrics/required_metrics.ts
@@ -13,12 +13,3 @@ export const nginx: InventoryMetric[] = [
   'nginxActiveConnections',
   'nginxRequestsPerConnection',
 ];
-
-export const aws: InventoryMetric[] = [
-  'awsOverview',
-  'awsCpuUtilization',
-  'awsNetworkBytes',
-  'awsNetworkPackets',
-  'awsDiskioOps',
-  'awsDiskioBytes',
-];
diff --git a/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_cpu_utilization.ts b/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_cpu_utilization.ts
deleted file mode 100644
index 51b9a4cfc7b3f..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_cpu_utilization.ts
+++ /dev/null
@@ -1,35 +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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const awsCpuUtilization: TSVBMetricModelCreator = (
-  timeField,
-  indexPattern
-): TSVBMetricModel => ({
-  id: 'awsCpuUtilization',
-  requires: ['aws.ec2'],
-  map_field_to: 'cloud.instance.id',
-  id_type: 'cloud',
-  index_pattern: indexPattern,
-  interval: '>=5m',
-  time_field: timeField,
-  type: 'timeseries',
-  series: [
-    {
-      id: 'cpu-util',
-      metrics: [
-        {
-          field: 'aws.ec2.cpu.total.pct',
-          id: 'avg-cpu-util',
-          type: 'avg',
-        },
-      ],
-      split_mode: 'everything',
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_diskio_bytes.ts b/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_diskio_bytes.ts
deleted file mode 100644
index 5224545c006b7..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_diskio_bytes.ts
+++ /dev/null
@@ -1,84 +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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const awsDiskioBytes: TSVBMetricModelCreator = (
-  timeField,
-  indexPattern
-): TSVBMetricModel => ({
-  id: 'awsDiskioBytes',
-  requires: ['aws.ec2'],
-  index_pattern: indexPattern,
-  map_field_to: 'cloud.instance.id',
-  id_type: 'cloud',
-  interval: '>=5m',
-  time_field: timeField,
-  type: 'timeseries',
-  series: [
-    {
-      id: 'writes',
-      metrics: [
-        {
-          field: 'aws.ec2.diskio.write.bytes',
-          id: 'sum-diskio-out',
-          type: 'sum',
-        },
-        {
-          id: 'csum-sum-diskio-out',
-          field: 'sum-diskio-out',
-          type: 'cumulative_sum',
-        },
-        {
-          id: 'deriv-csum-sum-diskio-out',
-          unit: '1s',
-          type: 'derivative',
-          field: 'csum-sum-diskio-out',
-        },
-        {
-          id: 'posonly-deriv-csum-sum-diskio-out',
-          field: 'deriv-csum-sum-diskio-out',
-          type: 'positive_only',
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'reads',
-      metrics: [
-        {
-          field: 'aws.ec2.diskio.read.bytes',
-          id: 'sum-diskio-in',
-          type: 'sum',
-        },
-        {
-          id: 'csum-sum-diskio-in',
-          field: 'sum-diskio-in',
-          type: 'cumulative_sum',
-        },
-        {
-          id: 'deriv-csum-sum-diskio-in',
-          unit: '1s',
-          type: 'derivative',
-          field: 'csum-sum-diskio-in',
-        },
-        {
-          id: 'posonly-deriv-csum-sum-diskio-in',
-          field: 'deriv-csum-sum-diskio-in',
-          type: 'positive_only',
-        },
-        {
-          id: 'inverted-posonly-deriv-csum-sum-diskio-in',
-          type: 'calculation',
-          variables: [{ id: 'var-rate', name: 'rate', field: 'posonly-deriv-csum-sum-diskio-in' }],
-          script: 'params.rate * -1',
-        },
-      ],
-      split_mode: 'everything',
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_diskio_ops.ts b/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_diskio_ops.ts
deleted file mode 100644
index c362a6d88c27a..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_diskio_ops.ts
+++ /dev/null
@@ -1,83 +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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const awsDiskioOps: TSVBMetricModelCreator = (timeField, indexPattern): TSVBMetricModel => ({
-  id: 'awsDiskioOps',
-  requires: ['aws.ec2'],
-  index_pattern: indexPattern,
-  map_field_to: 'cloud.instance.id',
-  id_type: 'cloud',
-  interval: '>=5m',
-  time_field: timeField,
-  type: 'timeseries',
-  series: [
-    {
-      id: 'writes',
-      metrics: [
-        {
-          field: 'aws.ec2.diskio.write.count',
-          id: 'sum-diskio-writes',
-          type: 'sum',
-        },
-        {
-          id: 'csum-sum-diskio-writes',
-          field: 'sum-diskio-writes',
-          type: 'cumulative_sum',
-        },
-        {
-          id: 'deriv-csum-sum-diskio-writes',
-          unit: '1s',
-          type: 'derivative',
-          field: 'csum-sum-diskio-writes',
-        },
-        {
-          id: 'posonly-deriv-csum-sum-diskio-writes',
-          field: 'deriv-csum-sum-diskio-writes',
-          type: 'positive_only',
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'reads',
-      metrics: [
-        {
-          field: 'aws.ec2.diskio.read.count',
-          id: 'sum-diskio-reads',
-          type: 'sum',
-        },
-        {
-          id: 'csum-sum-diskio-reads',
-          field: 'sum-diskio-reads',
-          type: 'cumulative_sum',
-        },
-        {
-          id: 'deriv-csum-sum-diskio-reads',
-          unit: '1s',
-          type: 'derivative',
-          field: 'csum-sum-diskio-reads',
-        },
-        {
-          id: 'posonly-deriv-csum-sum-diskio-reads',
-          field: 'deriv-csum-sum-diskio-reads',
-          type: 'positive_only',
-        },
-        {
-          id: 'inverted-posonly-deriv-csum-sum-diskio-reads',
-          type: 'calculation',
-          variables: [
-            { id: 'var-rate', name: 'rate', field: 'posonly-deriv-csum-sum-diskio-reads' },
-          ],
-          script: 'params.rate * -1',
-        },
-      ],
-      split_mode: 'everything',
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_network_bytes.ts b/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_network_bytes.ts
deleted file mode 100644
index b142feb95450c..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_network_bytes.ts
+++ /dev/null
@@ -1,86 +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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-// see discussion in: https://github.com/elastic/kibana/issues/42687
-
-export const awsNetworkBytes: TSVBMetricModelCreator = (
-  timeField,
-  indexPattern
-): TSVBMetricModel => ({
-  id: 'awsNetworkBytes',
-  requires: ['aws.ec2'],
-  index_pattern: indexPattern,
-  map_field_to: 'cloud.instance.id',
-  id_type: 'cloud',
-  interval: '>=5m',
-  time_field: timeField,
-  type: 'timeseries',
-  series: [
-    {
-      id: 'tx',
-      metrics: [
-        {
-          field: 'aws.ec2.network.out.bytes',
-          id: 'sum-net-out',
-          type: 'sum',
-        },
-        {
-          id: 'csum-sum-net-out',
-          field: 'sum-net-out',
-          type: 'cumulative_sum',
-        },
-        {
-          id: 'deriv-csum-sum-net-out',
-          unit: '1s',
-          type: 'derivative',
-          field: 'csum-sum-net-out',
-        },
-        {
-          id: 'posonly-deriv-csum-sum-net-out',
-          field: 'deriv-csum-sum-net-out',
-          type: 'positive_only',
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'rx',
-      metrics: [
-        {
-          field: 'aws.ec2.network.in.bytes',
-          id: 'sum-net-in',
-          type: 'sum',
-        },
-        {
-          id: 'csum-sum-net-in',
-          field: 'sum-net-in',
-          type: 'cumulative_sum',
-        },
-        {
-          id: 'deriv-csum-sum-net-in',
-          unit: '1s',
-          type: 'derivative',
-          field: 'csum-sum-net-in',
-        },
-        {
-          id: 'posonly-deriv-csum-sum-net-in',
-          field: 'deriv-csum-sum-net-in',
-          type: 'positive_only',
-        },
-        {
-          id: 'inverted-posonly-deriv-csum-sum-net-in',
-          type: 'calculation',
-          variables: [{ id: 'var-rate', name: 'rate', field: 'posonly-deriv-csum-sum-net-in' }],
-          script: 'params.rate * -1',
-        },
-      ],
-      split_mode: 'everything',
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_network_packets.ts b/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_network_packets.ts
deleted file mode 100644
index 9d39582b66864..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_network_packets.ts
+++ /dev/null
@@ -1,52 +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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const awsNetworkPackets: TSVBMetricModelCreator = (
-  timeField,
-  indexPattern
-): TSVBMetricModel => ({
-  id: 'awsNetworkPackets',
-  requires: ['aws.ec2'],
-  index_pattern: indexPattern,
-  map_field_to: 'cloud.instance.id',
-  id_type: 'cloud',
-  interval: '>=5m',
-  time_field: timeField,
-  type: 'timeseries',
-  series: [
-    {
-      id: 'packets-out',
-      metrics: [
-        {
-          field: 'aws.ec2.network.out.packets',
-          id: 'avg-net-out',
-          type: 'avg',
-        },
-      ],
-      split_mode: 'everything',
-    },
-    {
-      id: 'packets-in',
-      metrics: [
-        {
-          field: 'aws.ec2.network.in.packets',
-          id: 'avg-net-in',
-          type: 'avg',
-        },
-        {
-          id: 'inverted-avg-net-in',
-          type: 'calculation',
-          variables: [{ id: 'var-avg', name: 'avg', field: 'avg-net-in' }],
-          script: 'params.avg * -1',
-        },
-      ],
-      split_mode: 'everything',
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_overview.ts b/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_overview.ts
deleted file mode 100644
index 3fe12d62d3352..0000000000000
--- a/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_overview.ts
+++ /dev/null
@@ -1,65 +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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types';
-
-export const awsOverview: TSVBMetricModelCreator = (timeField, indexPattern): TSVBMetricModel => ({
-  id: 'awsOverview',
-  requires: ['aws.ec2'],
-  index_pattern: indexPattern,
-  map_field_to: 'cloud.instance.id',
-  id_type: 'cloud',
-  interval: '>=5m',
-  time_field: timeField,
-  type: 'top_n',
-  series: [
-    {
-      id: 'cpu-util',
-      split_mode: 'everything',
-      metrics: [
-        {
-          field: 'aws.ec2.cpu.total.pct',
-          id: 'cpu-total-pct',
-          type: 'max',
-        },
-      ],
-    },
-    {
-      id: 'status-check-failed',
-      split_mode: 'everything',
-      metrics: [
-        {
-          field: 'aws.ec2.status.check_failed',
-          id: 'status-check-failed',
-          type: 'max',
-        },
-      ],
-    },
-    {
-      id: 'packets-out',
-      split_mode: 'everything',
-      metrics: [
-        {
-          field: 'aws.ec2.network.out.packets',
-          id: 'network-out-packets',
-          type: 'avg',
-        },
-      ],
-    },
-    {
-      id: 'packets-in',
-      split_mode: 'everything',
-      metrics: [
-        {
-          field: 'aws.ec2.network.in.packets',
-          id: 'network-in-packets',
-          type: 'avg',
-        },
-      ],
-    },
-  ],
-});
diff --git a/x-pack/plugins/infra/common/inventory_models/types.ts b/x-pack/plugins/infra/common/inventory_models/types.ts
index 5bc36429e2ba2..fd52c1c230c17 100644
--- a/x-pack/plugins/infra/common/inventory_models/types.ts
+++ b/x-pack/plugins/infra/common/inventory_models/types.ts
@@ -37,21 +37,6 @@ export type InventoryFormatterType = rt.TypeOf<typeof InventoryFormatterTypeRT>;
 export type InventoryItemType = rt.TypeOf<typeof ItemTypeRT>;
 
 export const InventoryMetricRT = rt.keyof({
-  hostSystemOverview: null,
-  hostCpuUsage: null,
-  hostFilesystem: null,
-  hostK8sOverview: null,
-  hostK8sCpuCap: null,
-  hostK8sDiskCap: null,
-  hostK8sMemoryCap: null,
-  hostK8sPodCap: null,
-  hostLoad: null,
-  hostMemoryUsage: null,
-  hostNetworkTraffic: null,
-  hostDockerOverview: null,
-  hostDockerInfo: null,
-  hostDockerTop5ByCpu: null,
-  hostDockerTop5ByMemory: null,
   podOverview: null,
   podCpuUsage: null,
   podMemoryUsage: null,
@@ -71,12 +56,6 @@ export const InventoryMetricRT = rt.keyof({
   nginxRequestRate: null,
   nginxActiveConnections: null,
   nginxRequestsPerConnection: null,
-  awsOverview: null,
-  awsCpuUtilization: null,
-  awsNetworkBytes: null,
-  awsNetworkPackets: null,
-  awsDiskioBytes: null,
-  awsDiskioOps: null,
   awsEC2CpuUtilization: null,
   awsEC2NetworkTraffic: null,
   awsEC2DiskIOBytes: null,
@@ -377,7 +356,7 @@ export const SnapshotMetricTypeRT = rt.keyof(SnapshotMetricTypeKeys);
 export type SnapshotMetricType = rt.TypeOf<typeof SnapshotMetricTypeRT>;
 
 export interface InventoryMetrics {
-  tsvb: { [name: string]: TSVBMetricModelCreator };
+  tsvb?: { [name: string]: TSVBMetricModelCreator };
   snapshot: { [name: string]: MetricsUIAggregation | undefined };
   defaultSnapshot: SnapshotMetricType;
   /** This is used by the inventory view to calculate the appropriate amount of time for the metrics detail page. Some metris like awsS3 require multiple days where others like host only need an hour.*/
@@ -403,7 +382,7 @@ export interface InventoryModel {
     uptime: boolean;
   };
   metrics: InventoryMetrics;
-  requiredMetrics: InventoryMetric[];
+  requiredMetrics?: InventoryMetric[];
   tooltipMetrics: SnapshotMetricType[];
   nodeFilter?: object[];
 }
diff --git a/x-pack/plugins/infra/public/components/asset_details/hooks/use_metadata.ts b/x-pack/plugins/infra/public/components/asset_details/hooks/use_metadata.ts
index 2750394da702f..c06344270c2f0 100644
--- a/x-pack/plugins/infra/public/components/asset_details/hooks/use_metadata.ts
+++ b/x-pack/plugins/infra/public/components/asset_details/hooks/use_metadata.ts
@@ -15,16 +15,23 @@ import { throwErrors, createPlainError } from '../../../../common/runtime_types'
 import { getFilteredMetrics } from '../../../pages/metrics/metric_detail/lib/get_filtered_metrics';
 import type { InventoryItemType, InventoryMetric } from '../../../../common/inventory_models/types';
 
-export function useMetadata(
-  nodeId: string,
-  nodeType: InventoryItemType,
-  requiredMetrics: InventoryMetric[],
-  sourceId: string,
+interface UseMetadataProps {
+  assetId: string;
+  assetType: InventoryItemType;
+  requiredMetrics?: InventoryMetric[];
+  sourceId: string;
   timeRange: {
     from: number;
     to: number;
-  }
-) {
+  };
+}
+export function useMetadata({
+  assetId,
+  assetType,
+  sourceId,
+  timeRange,
+  requiredMetrics = [],
+}: UseMetadataProps) {
   const decodeResponse = (response: any) => {
     return pipe(InfraMetadataRT.decode(response), fold(throwErrors(createPlainError), identity));
   };
@@ -32,8 +39,8 @@ export function useMetadata(
     '/api/infra/metadata',
     'POST',
     JSON.stringify({
-      nodeId,
-      nodeType,
+      nodeId: assetId,
+      nodeType: assetType,
       sourceId,
       timeRange,
     }),
@@ -49,17 +56,13 @@ export function useMetadata(
   return {
     name: (response && response.name) || '',
     filteredRequiredMetrics:
-      (response && getFilteredMetrics(requiredMetrics, response.features)) || [],
+      response && requiredMetrics.length > 0
+        ? getFilteredMetrics(requiredMetrics, response.features)
+        : [],
     error: (error && error.message) || null,
     loading,
     metadata: response,
-    cloudId:
-      (response &&
-        response.info &&
-        response.info.cloud &&
-        response.info.cloud.instance &&
-        response.info.cloud.instance.id) ||
-      '',
+    cloudId: response?.info?.cloud?.instance?.id || '',
     reload: makeRequest,
   };
 }
diff --git a/x-pack/plugins/infra/public/components/asset_details/hooks/use_metadata_state.ts b/x-pack/plugins/infra/public/components/asset_details/hooks/use_metadata_state.ts
index 4d0f0c66f67c8..0df9ae9a152dd 100644
--- a/x-pack/plugins/infra/public/components/asset_details/hooks/use_metadata_state.ts
+++ b/x-pack/plugins/infra/public/components/asset_details/hooks/use_metadata_state.ts
@@ -7,7 +7,6 @@
 
 import { useEffect, useCallback } from 'react';
 import createContainer from 'constate';
-import { findInventoryModel } from '../../../../common/inventory_models';
 import { useSourceContext } from '../../../containers/metrics_source';
 import { useMetadata } from './use_metadata';
 import { AssetDetailsProps } from '../types';
@@ -19,16 +18,14 @@ export type UseMetadataProviderProps = Pick<AssetDetailsProps, 'asset' | 'assetT
 export function useMetadataProvider({ asset, assetType }: UseMetadataProviderProps) {
   const [, setUrlState] = useAssetDetailsUrlState();
   const { getDateRangeInTimestamp } = useDateRangeProviderContext();
-  const inventoryModel = findInventoryModel(assetType);
   const { sourceId } = useSourceContext();
 
-  const { loading, error, metadata, reload } = useMetadata(
-    asset.id,
+  const { loading, error, metadata, reload } = useMetadata({
+    assetId: asset.id,
     assetType,
-    inventoryModel.requiredMetrics,
     sourceId,
-    getDateRangeInTimestamp()
-  );
+    timeRange: getDateRangeInTimestamp(),
+  });
 
   const refresh = useCallback(() => {
     reload();
diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/osquery/index.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/osquery/index.tsx
index 26ff945d04025..7339043ffa98f 100644
--- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/osquery/index.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/osquery/index.tsx
@@ -11,7 +11,6 @@ import React, { useMemo } from 'react';
 import { useKibanaContextForPlugin } from '../../../../../../../hooks/use_kibana';
 import { TabContent, TabProps } from '../shared';
 import { useSourceContext } from '../../../../../../../containers/metrics_source';
-import { findInventoryModel } from '../../../../../../../../common/inventory_models';
 import { InventoryItemType } from '../../../../../../../../common/inventory_models/types';
 import { useMetadata } from '../../../../../../../components/asset_details/hooks/use_metadata';
 import { useWaffleTimeContext } from '../../../../hooks/use_waffle_time';
@@ -19,16 +18,14 @@ import { useWaffleTimeContext } from '../../../../hooks/use_waffle_time';
 const TabComponent = (props: TabProps) => {
   const nodeId = props.node.id;
   const nodeType = props.nodeType as InventoryItemType;
-  const inventoryModel = findInventoryModel(nodeType);
   const { sourceId } = useSourceContext();
   const { currentTimeRange } = useWaffleTimeContext();
-  const { loading, metadata } = useMetadata(
-    nodeId,
-    nodeType,
-    inventoryModel.requiredMetrics,
+  const { loading, metadata } = useMetadata({
+    assetId: nodeId,
+    assetType: nodeType,
     sourceId,
-    currentTimeRange
-  );
+    timeRange: currentTimeRange,
+  });
   const {
     services: { osquery },
   } = useKibanaContextForPlugin();
diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/properties/index.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/properties/index.tsx
index bc0ee279b2cbd..c8550a4d05163 100644
--- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/properties/index.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/properties/index.tsx
@@ -11,7 +11,6 @@ import { EuiLoadingChart } from '@elastic/eui';
 import { euiStyled } from '@kbn/kibana-react-plugin/common';
 import { TabContent, TabProps } from '../shared';
 import { useSourceContext } from '../../../../../../../containers/metrics_source';
-import { findInventoryModel } from '../../../../../../../../common/inventory_models';
 import { InventoryItemType } from '../../../../../../../../common/inventory_models/types';
 import { useMetadata } from '../../../../../../../components/asset_details/hooks/use_metadata';
 import { getFields } from './build_fields';
@@ -22,17 +21,15 @@ import { useWaffleFiltersContext } from '../../../../hooks/use_waffle_filters';
 const TabComponent = (props: TabProps) => {
   const nodeId = props.node.id;
   const nodeType = props.nodeType as InventoryItemType;
-  const inventoryModel = findInventoryModel(nodeType);
   const { sourceId } = useSourceContext();
   const { currentTimeRange } = useWaffleTimeContext();
   const { applyFilterQuery } = useWaffleFiltersContext();
-  const { loading: metadataLoading, metadata } = useMetadata(
-    nodeId,
-    nodeType,
-    inventoryModel.requiredMetrics,
+  const { loading: metadataLoading, metadata } = useMetadata({
+    assetId: nodeId,
+    assetType: nodeType,
     sourceId,
-    currentTimeRange
-  );
+    timeRange: currentTimeRange,
+  });
 
   const hostFields = useMemo(() => {
     if (!metadata) return null;
diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/layout.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/layout.tsx
index a2b48e9b7f2f3..c89fa6f7fe601 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/layout.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/layout.tsx
@@ -13,7 +13,6 @@ import { AwsRDSLayout } from './layouts/aws_rds_layout';
 import { AwsS3Layout } from './layouts/aws_s3_layout';
 import { AwsSQSLayout } from './layouts/aws_sqs_layout';
 import { ContainerLayout } from './layouts/container_layout';
-import { HostLayout } from './layouts/host_layout';
 import { PodLayout } from './layouts/pod_layout';
 
 export const Layout = ({
@@ -31,9 +30,9 @@ export const Layout = ({
       return <AwsSQSLayout {...layoutProps} />;
     case 'container':
       return <ContainerLayout {...layoutProps} />;
-    case 'host':
-      return <HostLayout {...layoutProps} />;
     case 'pod':
       return <PodLayout {...layoutProps} />;
+    default:
+      throw new Error(`${inventoryItemType} is not supported.`);
   }
 };
diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/layouts/aws_layout_sections.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/layouts/aws_layout_sections.tsx
deleted file mode 100644
index 75c06c30b968a..0000000000000
--- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/layouts/aws_layout_sections.tsx
+++ /dev/null
@@ -1,242 +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 { i18n } from '@kbn/i18n';
-import { withTheme } from '@kbn/kibana-react-plugin/common';
-import React from 'react';
-import type { LayoutPropsWithTheme } from '../../types';
-import { ChartSectionVis } from '../chart_section_vis';
-import { GaugesSectionVis } from '../gauges_section_vis';
-import { Section } from '../section';
-import { SubSection } from '../sub_section';
-
-export const AwsLayoutSection = withTheme(
-  ({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
-    <React.Fragment>
-      <Section
-        navLabel="AWS"
-        sectionLabel={i18n.translate(
-          'xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.sectionLabel',
-          {
-            defaultMessage: 'AWS Overview',
-          }
-        )}
-        metrics={metrics}
-        onChangeRangeTime={onChangeRangeTime}
-      >
-        <SubSection id="awsOverview">
-          <GaugesSectionVis
-            seriesOverrides={{
-              'cpu-util': {
-                name: i18n.translate(
-                  'xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.cpuUtilizationSeriesLabel',
-                  {
-                    defaultMessage: 'CPU Utilization',
-                  }
-                ),
-                color: theme.eui.euiColorFullShade,
-                formatter: 'percent',
-                gaugeMax: 1,
-              },
-              'status-check-failed': {
-                name: i18n.translate(
-                  'xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.statusCheckFailedLabel',
-                  {
-                    defaultMessage: 'Status check failed',
-                  }
-                ),
-                color: theme.eui.euiColorFullShade,
-              },
-              'packets-in': {
-                name: i18n.translate(
-                  'xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.networkPacketsInLabel',
-                  {
-                    defaultMessage: 'Packets (in)',
-                  }
-                ),
-                color: theme.eui.euiColorFullShade,
-                formatter: 'number',
-              },
-              'packets-out': {
-                name: i18n.translate(
-                  'xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.networkPacketsOutLabel',
-                  {
-                    defaultMessage: 'Packets (out)',
-                  }
-                ),
-                color: theme.eui.euiColorFullShade,
-                formatter: 'number',
-              },
-            }}
-          />
-        </SubSection>
-        <SubSection
-          id="awsCpuUtilization"
-          label={i18n.translate(
-            'xpack.infra.metricDetailPage.awsMetricsLayout.cpuUtilSection.sectionLabel',
-            {
-              defaultMessage: 'CPU Utilization',
-            }
-          )}
-        >
-          <ChartSectionVis
-            type="area"
-            formatter="number"
-            seriesOverrides={{
-              'cpu-util': {
-                color: theme.eui.euiColorVis1,
-                name: i18n.translate(
-                  'xpack.infra.metricDetailPage.awsMetricsLayout.cpuUtilSection.percentSeriesLabel',
-                  {
-                    defaultMessage: 'percent',
-                  }
-                ),
-              },
-            }}
-          />
-        </SubSection>
-        <SubSection
-          id="awsNetworkBytes"
-          label={i18n.translate(
-            'xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.sectionLabel',
-            {
-              defaultMessage: 'Network Traffic',
-            }
-          )}
-        >
-          <ChartSectionVis
-            type="area"
-            formatter="bits"
-            formatterTemplate="{{value}}/s"
-            seriesOverrides={{
-              tx: {
-                color: theme.eui.euiColorVis1,
-                name: i18n.translate(
-                  'xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.txSeriesLabel',
-                  {
-                    defaultMessage: 'out',
-                  }
-                ),
-              },
-              rx: {
-                color: theme.eui.euiColorVis2,
-                name: i18n.translate(
-                  'xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.rxSeriesLabel',
-                  {
-                    defaultMessage: 'in',
-                  }
-                ),
-              },
-            }}
-          />
-        </SubSection>
-        <SubSection
-          id="awsNetworkPackets"
-          label={i18n.translate(
-            'xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.sectionLabel',
-            {
-              defaultMessage: 'Network Packets (Average)',
-            }
-          )}
-        >
-          <ChartSectionVis
-            type="area"
-            formatter="number"
-            seriesOverrides={{
-              'packets-out': {
-                color: theme.eui.euiColorVis1,
-                name: i18n.translate(
-                  'xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.packetsOutSeriesLabel',
-                  {
-                    defaultMessage: 'out',
-                  }
-                ),
-              },
-              'packets-in': {
-                color: theme.eui.euiColorVis2,
-                name: i18n.translate(
-                  'xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.packetsInSeriesLabel',
-                  {
-                    defaultMessage: 'in',
-                  }
-                ),
-              },
-            }}
-          />
-        </SubSection>
-        <SubSection
-          id="awsDiskioOps"
-          label={i18n.translate(
-            'xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.sectionLabel',
-            {
-              defaultMessage: 'Disk I/O Operations',
-            }
-          )}
-        >
-          <ChartSectionVis
-            type="area"
-            formatter="number"
-            seriesOverrides={{
-              writes: {
-                color: theme.eui.euiColorVis1,
-                name: i18n.translate(
-                  'xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.writesSeriesLabel',
-                  {
-                    defaultMessage: 'writes',
-                  }
-                ),
-              },
-              reads: {
-                color: theme.eui.euiColorVis2,
-                name: i18n.translate(
-                  'xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.readsSeriesLabel',
-                  {
-                    defaultMessage: 'reads',
-                  }
-                ),
-              },
-            }}
-          />
-        </SubSection>
-        <SubSection
-          id="awsDiskioBytes"
-          label={i18n.translate(
-            'xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.sectionLabel',
-            {
-              defaultMessage: 'Disk I/O Bytes',
-            }
-          )}
-        >
-          <ChartSectionVis
-            type="area"
-            formatter="number"
-            seriesOverrides={{
-              writes: {
-                color: theme.eui.euiColorVis1,
-                name: i18n.translate(
-                  'xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.writesSeriesLabel',
-                  {
-                    defaultMessage: 'writes',
-                  }
-                ),
-              },
-              reads: {
-                color: theme.eui.euiColorVis2,
-                name: i18n.translate(
-                  'xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.readsSeriesLabel',
-                  {
-                    defaultMessage: 'reads',
-                  }
-                ),
-              },
-            }}
-          />
-        </SubSection>
-      </Section>
-    </React.Fragment>
-  )
-);
diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/layouts/host_layout.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/layouts/host_layout.tsx
deleted file mode 100644
index 41e0204bc7b29..0000000000000
--- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/layouts/host_layout.tsx
+++ /dev/null
@@ -1,376 +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 { EuiPanel } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import { withTheme } from '@kbn/kibana-react-plugin/common';
-import React from 'react';
-import type { LayoutPropsWithTheme } from '../../types';
-import { ChartSectionVis } from '../chart_section_vis';
-import { GaugesSectionVis } from '../gauges_section_vis';
-import { MetadataDetails } from '../metadata_details';
-import { Section } from '../section';
-import { SubSection } from '../sub_section';
-import { AwsLayoutSection } from './aws_layout_sections';
-import { NginxLayoutSection } from './nginx_layout_sections';
-
-export const HostLayout = withTheme(
-  ({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
-    <React.Fragment>
-      <MetadataDetails
-        fields={[
-          'host.hostname',
-          'host.os.name',
-          'host.os.kernel',
-          'host.containerized',
-          'cloud.provider',
-          'cloud.availability_zone',
-          'cloud.machine.type',
-          'cloud.project.id',
-          'cloud.instance.id',
-          'cloud.instance.name',
-        ]}
-      />
-      <EuiPanel>
-        <Section
-          navLabel={i18n.translate('xpack.infra.metricDetailPage.hostMetricsLayout.layoutLabel', {
-            defaultMessage: 'Host',
-          })}
-          sectionLabel={i18n.translate(
-            'xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.sectionLabel',
-            {
-              defaultMessage: 'Host Overview',
-            }
-          )}
-          metrics={metrics}
-          onChangeRangeTime={onChangeRangeTime}
-        >
-          <SubSection id="hostSystemOverview">
-            <GaugesSectionVis
-              seriesOverrides={{
-                cpu: {
-                  name: i18n.translate(
-                    'xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.cpuUsageSeriesLabel',
-                    {
-                      defaultMessage: 'CPU Usage',
-                    }
-                  ),
-                  color: theme.eui.euiColorFullShade,
-                  formatter: 'percent',
-                  gaugeMax: 1,
-                },
-                load: {
-                  name: i18n.translate(
-                    'xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.loadSeriesLabel',
-                    {
-                      defaultMessage: 'Load (5m)',
-                    }
-                  ),
-                  color: theme.eui.euiColorFullShade,
-                },
-                memory: {
-                  name: i18n.translate(
-                    'xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.memoryCapacitySeriesLabel',
-                    {
-                      defaultMessage: 'Memory Usage',
-                    }
-                  ),
-                  color: theme.eui.euiColorFullShade,
-                  formatter: 'percent',
-                  gaugeMax: 1,
-                },
-                rx: {
-                  name: i18n.translate(
-                    'xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.inboundRXSeriesLabel',
-                    {
-                      defaultMessage: 'Inbound (RX)',
-                    }
-                  ),
-                  color: theme.eui.euiColorFullShade,
-                  formatter: 'bits',
-                  formatterTemplate: '{{value}}/s',
-                },
-                tx: {
-                  name: i18n.translate(
-                    'xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.outboundTXSeriesLabel',
-                    {
-                      defaultMessage: 'Outbound (TX)',
-                    }
-                  ),
-                  color: theme.eui.euiColorFullShade,
-                  formatter: 'bits',
-                  formatterTemplate: '{{value}}/s',
-                },
-              }}
-            />
-          </SubSection>
-          <SubSection
-            id="hostCpuUsage"
-            label={i18n.translate(
-              'xpack.infra.metricDetailPage.hostMetricsLayout.cpuUsageSection.sectionLabel',
-              {
-                defaultMessage: 'CPU Usage',
-              }
-            )}
-          >
-            <ChartSectionVis
-              stacked={true}
-              type="area"
-              formatter="percent"
-              seriesOverrides={{
-                user: { color: theme.eui.euiColorVis0 },
-                system: { color: theme.eui.euiColorVis2 },
-                steal: { color: theme.eui.euiColorVis9 },
-                irq: { color: theme.eui.euiColorVis4 },
-                softirq: { color: theme.eui.euiColorVis6 },
-                iowait: { color: theme.eui.euiColorVis7 },
-                nice: { color: theme.eui.euiColorVis5 },
-              }}
-            />
-          </SubSection>
-          <SubSection
-            id="hostLoad"
-            label={i18n.translate(
-              'xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.sectionLabel',
-              {
-                defaultMessage: 'Load',
-              }
-            )}
-          >
-            <ChartSectionVis
-              seriesOverrides={{
-                load_1m: {
-                  color: theme.eui.euiColorVis0,
-                  name: i18n.translate(
-                    'xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.oneMinuteSeriesLabel',
-                    {
-                      defaultMessage: '1m',
-                    }
-                  ),
-                },
-                load_5m: {
-                  color: theme.eui.euiColorVis1,
-                  name: i18n.translate(
-                    'xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.fiveMinuteSeriesLabel',
-                    {
-                      defaultMessage: '5m',
-                    }
-                  ),
-                },
-                load_15m: {
-                  color: theme.eui.euiColorVis3,
-                  name: i18n.translate(
-                    'xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.fifteenMinuteSeriesLabel',
-                    {
-                      defaultMessage: '15m',
-                    }
-                  ),
-                },
-              }}
-            />
-          </SubSection>
-          <SubSection
-            id="hostMemoryUsage"
-            label={i18n.translate(
-              'xpack.infra.metricDetailPage.hostMetricsLayout.memoryUsageSection.sectionLabel',
-              {
-                defaultMessage: 'Memory Usage',
-              }
-            )}
-          >
-            <ChartSectionVis
-              stacked={true}
-              formatter="bytes"
-              type="area"
-              seriesOverrides={{
-                used: { color: theme.eui.euiColorVis2 },
-                free: { color: theme.eui.euiColorVis0 },
-                cache: { color: theme.eui.euiColorVis1 },
-              }}
-            />
-          </SubSection>
-          <SubSection
-            id="hostNetworkTraffic"
-            label={i18n.translate(
-              'xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.sectionLabel',
-              {
-                defaultMessage: 'Network Traffic',
-              }
-            )}
-          >
-            <ChartSectionVis
-              formatter="bits"
-              formatterTemplate="{{value}}/s"
-              type="area"
-              seriesOverrides={{
-                rx: {
-                  color: theme.eui.euiColorVis1,
-                  name: i18n.translate(
-                    'xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.networkRxRateSeriesLabel',
-                    {
-                      defaultMessage: 'in',
-                    }
-                  ),
-                },
-                tx: {
-                  color: theme.eui.euiColorVis2,
-                  name: i18n.translate(
-                    'xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.networkTxRateSeriesLabel',
-                    {
-                      defaultMessage: 'out',
-                    }
-                  ),
-                },
-              }}
-            />
-          </SubSection>
-        </Section>
-        <Section
-          navLabel="Kubernetes"
-          sectionLabel={i18n.translate(
-            'xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.sectionLabel',
-            {
-              defaultMessage: 'Kubernetes Overview',
-            }
-          )}
-          metrics={metrics}
-          onChangeRangeTime={onChangeRangeTime}
-        >
-          <SubSection id="hostK8sOverview">
-            <GaugesSectionVis
-              seriesOverrides={{
-                cpucap: {
-                  name: i18n.translate(
-                    'xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.cpuUsageSeriesLabel',
-                    {
-                      defaultMessage: 'CPU Capacity',
-                    }
-                  ),
-                  color: 'success',
-                  formatter: 'percent',
-                  gaugeMax: 1,
-                },
-                load: {
-                  name: i18n.translate(
-                    'xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.loadSeriesLabel',
-                    {
-                      defaultMessage: 'Load (5m)',
-                    }
-                  ),
-                  color: 'success',
-                },
-                memorycap: {
-                  name: i18n.translate(
-                    'xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.memoryUsageSeriesLabel',
-                    {
-                      defaultMessage: 'Memory Capacity',
-                    }
-                  ),
-                  color: 'success',
-                  formatter: 'percent',
-                  gaugeMax: 1,
-                },
-                podcap: {
-                  name: i18n.translate(
-                    'xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.podCapacitySeriesLabel',
-                    {
-                      defaultMessage: 'Pod Capacity',
-                    }
-                  ),
-                  color: 'success',
-                  formatter: 'percent',
-                  gaugeMax: 1,
-                },
-                diskcap: {
-                  name: i18n.translate(
-                    'xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.diskCapacitySeriesLabel',
-                    {
-                      defaultMessage: 'Disk Capacity',
-                    }
-                  ),
-                  color: 'success',
-                  formatter: 'percent',
-                  gaugeMax: 1,
-                },
-              }}
-            />
-          </SubSection>
-          <SubSection
-            id="hostK8sCpuCap"
-            label={i18n.translate(
-              'xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeCpuCapacitySection.sectionLabel',
-              {
-                defaultMessage: 'Node CPU Capacity',
-              }
-            )}
-          >
-            <ChartSectionVis
-              formatter="abbreviatedNumber"
-              seriesOverrides={{
-                capacity: { color: theme.eui.euiColorVis2 },
-                used: { color: theme.eui.euiColorVis1, type: 'area' },
-              }}
-            />
-          </SubSection>
-          <SubSection
-            id="hostK8sMemoryCap"
-            label={i18n.translate(
-              'xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeMemoryCapacitySection.sectionLabel',
-              {
-                defaultMessage: 'Node Memory Capacity',
-              }
-            )}
-          >
-            <ChartSectionVis
-              formatter="bytes"
-              seriesOverrides={{
-                capacity: { color: theme.eui.euiColorVis2 },
-                used: { color: theme.eui.euiColorVis1, type: 'area' },
-              }}
-            />
-          </SubSection>
-          <SubSection
-            id="hostK8sDiskCap"
-            label={i18n.translate(
-              'xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeDiskCapacitySection.sectionLabel',
-              {
-                defaultMessage: 'Node Disk Capacity',
-              }
-            )}
-          >
-            <ChartSectionVis
-              formatter="bytes"
-              seriesOverrides={{
-                capacity: { color: theme.eui.euiColorVis2 },
-                used: { color: theme.eui.euiColorVis1, type: 'area' },
-              }}
-            />
-          </SubSection>
-          <SubSection
-            id="hostK8sPodCap"
-            label={i18n.translate(
-              'xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodePodCapacitySection.sectionLabel',
-              {
-                defaultMessage: 'Node Pod Capacity',
-              }
-            )}
-          >
-            <ChartSectionVis
-              formatter="number"
-              seriesOverrides={{
-                capacity: { color: theme.eui.euiColorVis2 },
-                used: { color: theme.eui.euiColorVis1, type: 'area' },
-              }}
-            />
-          </SubSection>
-        </Section>
-        <AwsLayoutSection metrics={metrics} onChangeRangeTime={onChangeRangeTime} />
-        <NginxLayoutSection metrics={metrics} onChangeRangeTime={onChangeRangeTime} />
-      </EuiPanel>
-    </React.Fragment>
-  )
-);
diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/metric_detail_page.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/metric_detail_page.tsx
index 1f049814a23d3..15425ef618049 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/metric_detail_page.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/metric_detail_page.tsx
@@ -42,7 +42,13 @@ export const MetricDetailPage = () => {
     loading: metadataLoading,
     cloudId,
     metadata,
-  } = useMetadata(nodeId, nodeType, inventoryModel.requiredMetrics, sourceId, parsedTimeRange);
+  } = useMetadata({
+    assetId: nodeId,
+    assetType: nodeType,
+    requiredMetrics: inventoryModel.requiredMetrics,
+    sourceId,
+    timeRange: parsedTimeRange,
+  });
 
   const [sideNav, setSideNav] = useState<NavItem[]>([]);
 
diff --git a/x-pack/plugins/infra/server/routes/node_details/index.ts b/x-pack/plugins/infra/server/routes/node_details/index.ts
index cd92c902a110e..56eccbbe160e9 100644
--- a/x-pack/plugins/infra/server/routes/node_details/index.ts
+++ b/x-pack/plugins/infra/server/routes/node_details/index.ts
@@ -34,30 +34,46 @@ export const initNodeDetailsRoute = (libs: InfraBackendLibs) => {
       },
     },
     async (requestContext, request, response) => {
-      const { nodeId, cloudId, nodeType, metrics, timerange, sourceId } = pipe(
-        NodeDetailsRequestRT.decode(request.body),
-        fold(throwErrors(Boom.badRequest), identity)
-      );
-      const soClient = (await requestContext.core).savedObjects.client;
-      const source = await libs.sources.getSourceConfiguration(soClient, sourceId);
-
-      UsageCollector.countNode(nodeType);
-
-      const options: InfraMetricsRequestOptions = {
-        nodeIds: {
-          nodeId,
-          cloudId,
-        },
-        nodeType,
-        sourceConfiguration: source.configuration,
-        metrics,
-        timerange,
-      };
-      return response.ok({
-        body: NodeDetailsMetricDataResponseRT.encode({
-          metrics: await libs.metrics.getMetrics(requestContext, options, request),
-        }),
-      });
+      try {
+        const { nodeId, cloudId, nodeType, metrics, timerange, sourceId } = pipe(
+          NodeDetailsRequestRT.decode(request.body),
+          fold(throwErrors(Boom.badRequest), identity)
+        );
+        const soClient = (await requestContext.core).savedObjects.client;
+        const source = await libs.sources.getSourceConfiguration(soClient, sourceId);
+
+        UsageCollector.countNode(nodeType);
+
+        const options: InfraMetricsRequestOptions = {
+          nodeIds: {
+            nodeId,
+            cloudId,
+          },
+          nodeType,
+          sourceConfiguration: source.configuration,
+          metrics,
+          timerange,
+        };
+        return response.ok({
+          body: NodeDetailsMetricDataResponseRT.encode({
+            metrics: await libs.metrics.getMetrics(requestContext, options, request),
+          }),
+        });
+      } catch (err) {
+        if (Boom.isBoom(err)) {
+          return response.customError({
+            statusCode: err.output.statusCode,
+            body: { message: err.output.payload.message },
+          });
+        }
+
+        return response.customError({
+          statusCode: err.statusCode ?? 500,
+          body: {
+            message: err.message ?? 'An unexpected error occurred',
+          },
+        });
+      }
     }
   );
 };
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index 260328513d6c1..b468f00d02466 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -18996,25 +18996,6 @@
     "xpack.infra.metadataEmbeddable.setFilterByValueTooltip": "Filtrer par valeur",
     "xpack.infra.metadataEmbeddable.setRemoveFilterTooltip": "Supprimer le filtre",
     "xpack.infra.metadataEmbeddable.value": "Valeur",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.cpuUtilSection.percentSeriesLabel": "pour cent",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.cpuUtilSection.sectionLabel": "Utilisation CPU",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.readsSeriesLabel": "lit",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.sectionLabel": "Octets d'E/S sur le disque",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.writesSeriesLabel": "écrit",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.readsSeriesLabel": "lit",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.sectionLabel": "Opérations d'E/S sur le disque",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.writesSeriesLabel": "écrit",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.rxSeriesLabel": "entrée",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.sectionLabel": "Trafic réseau",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.txSeriesLabel": "sortie",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.packetsInSeriesLabel": "entrée",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.packetsOutSeriesLabel": "sortie",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.sectionLabel": "Paquets réseau (moyenne)",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.cpuUtilizationSeriesLabel": "Utilisation CPU",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.networkPacketsInLabel": "Paquets (entrée)",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.networkPacketsOutLabel": "Paquets (sortie)",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.sectionLabel": "Aperçu AWS",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.statusCheckFailedLabel": "La vérification du statut a échoué",
     "xpack.infra.metricDetailPage.containerMetricsLayout.cpuUsageSection.sectionLabel": "Utilisation CPU",
     "xpack.infra.metricDetailPage.containerMetricsLayout.diskIoBytesSection.readRateSeriesLabel": "lit",
     "xpack.infra.metricDetailPage.containerMetricsLayout.diskIoBytesSection.sectionLabel": "ES sur le disque (octets)",
@@ -19039,32 +19020,6 @@
     "xpack.infra.metricDetailPage.ec2MetricsLayout.diskIOBytesSection.writeLabel": "écrit",
     "xpack.infra.metricDetailPage.ec2MetricsLayout.networkTrafficSection.sectionLabel": "Trafic réseau",
     "xpack.infra.metricDetailPage.ec2MetricsLayout.overviewSection.sectionLabel": "Aperçu EC2 AWS",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.cpuUsageSection.sectionLabel": "Utilisation CPU",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.layoutLabel": "Hôte",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.fifteenMinuteSeriesLabel": "15 min",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.fiveMinuteSeriesLabel": "5 min",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.oneMinuteSeriesLabel": "1 min",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.sectionLabel": "Charge",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.memoryUsageSection.sectionLabel": "Utilisation mémoire",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.networkRxRateSeriesLabel": "entrée",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.networkTxRateSeriesLabel": "sortie",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.sectionLabel": "Trafic réseau",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.cpuUsageSeriesLabel": "Utilisation CPU",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.inboundRXSeriesLabel": "Entrant (RX)",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.loadSeriesLabel": "Charge (5 min)",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.memoryCapacitySeriesLabel": "Utilisation mémoire",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.outboundTXSeriesLabel": "Sortant (TX)",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.sectionLabel": "Aperçu de l'hôte",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeCpuCapacitySection.sectionLabel": "Capacité CPU du nœud",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeDiskCapacitySection.sectionLabel": "Capacité du disque du nœud",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeMemoryCapacitySection.sectionLabel": "Capacité de mémoire du nœud",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodePodCapacitySection.sectionLabel": "Capacité de pod du nœud",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.cpuUsageSeriesLabel": "Capacité CPU",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.diskCapacitySeriesLabel": "Capacité du disque",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.loadSeriesLabel": "Charge (5 min)",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.memoryUsageSeriesLabel": "Capacité de mémoire",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.podCapacitySeriesLabel": "Capacité de pod",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.sectionLabel": "Aperçu Kubernetes",
     "xpack.infra.metricDetailPage.nginxMetricsLayout.activeConnectionsSection.sectionLabel": "Connexions actives",
     "xpack.infra.metricDetailPage.nginxMetricsLayout.hitsSection.sectionLabel": "Résultats",
     "xpack.infra.metricDetailPage.nginxMetricsLayout.requestRateSection.sectionLabel": "Taux de requêtes",
@@ -39848,4 +39803,4 @@
     "xpack.painlessLab.walkthroughButtonLabel": "Présentation",
     "xpack.serverlessObservability.nav.getStarted": "Démarrer"
   }
-}
+}
\ No newline at end of file
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index db4e8cc674281..7f59456c6bd14 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -19010,25 +19010,6 @@
     "xpack.infra.metadataEmbeddable.setFilterByValueTooltip": "値でフィルタリング",
     "xpack.infra.metadataEmbeddable.setRemoveFilterTooltip": "フィルターを削除",
     "xpack.infra.metadataEmbeddable.value": "値",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.cpuUtilSection.percentSeriesLabel": "パーセント",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.cpuUtilSection.sectionLabel": "CPU 使用状況",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.readsSeriesLabel": "読み取り",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.sectionLabel": "ディスク I/O バイト",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.writesSeriesLabel": "書き込み",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.readsSeriesLabel": "読み取り",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.sectionLabel": "ディスク I/O オペレーション",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.writesSeriesLabel": "書き込み",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.rxSeriesLabel": "in",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.sectionLabel": "ネットワークトラフィック",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.txSeriesLabel": "出",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.packetsInSeriesLabel": "in",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.packetsOutSeriesLabel": "出",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.sectionLabel": "ネットワークパケット(平均)",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.cpuUtilizationSeriesLabel": "CPU 使用状況",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.networkPacketsInLabel": "パケット(受信)",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.networkPacketsOutLabel": "パケット(送信)",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.sectionLabel": "AWS概要",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.statusCheckFailedLabel": "ステータス確認失敗",
     "xpack.infra.metricDetailPage.containerMetricsLayout.cpuUsageSection.sectionLabel": "CPU使用状況",
     "xpack.infra.metricDetailPage.containerMetricsLayout.diskIoBytesSection.readRateSeriesLabel": "読み取り",
     "xpack.infra.metricDetailPage.containerMetricsLayout.diskIoBytesSection.sectionLabel": "ディスク IO(バイト)",
@@ -19053,32 +19034,6 @@
     "xpack.infra.metricDetailPage.ec2MetricsLayout.diskIOBytesSection.writeLabel": "書き込み",
     "xpack.infra.metricDetailPage.ec2MetricsLayout.networkTrafficSection.sectionLabel": "ネットワークトラフィック",
     "xpack.infra.metricDetailPage.ec2MetricsLayout.overviewSection.sectionLabel": "Aws EC2概要",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.cpuUsageSection.sectionLabel": "CPU使用状況",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.layoutLabel": "ホスト",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.fifteenMinuteSeriesLabel": "15m",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.fiveMinuteSeriesLabel": "5m",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.oneMinuteSeriesLabel": "1m",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.sectionLabel": "読み込み",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.memoryUsageSection.sectionLabel": "メモリー使用状況",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.networkRxRateSeriesLabel": "in",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.networkTxRateSeriesLabel": "出",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.sectionLabel": "ネットワークトラフィック",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.cpuUsageSeriesLabel": "CPU使用状況",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.inboundRXSeriesLabel": "受信(RX)",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.loadSeriesLabel": "読み込み(5m)",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.memoryCapacitySeriesLabel": "メモリー使用状況",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.outboundTXSeriesLabel": "送信(TX)",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.sectionLabel": "ホスト概要",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeCpuCapacitySection.sectionLabel": "ノード CPU 処理能力",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeDiskCapacitySection.sectionLabel": "ノードディスク容量",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeMemoryCapacitySection.sectionLabel": "ノードメモリー容量",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodePodCapacitySection.sectionLabel": "ノードポッド容量",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.cpuUsageSeriesLabel": "CPU 処理能力",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.diskCapacitySeriesLabel": "ディスク容量",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.loadSeriesLabel": "読み込み(5m)",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.memoryUsageSeriesLabel": "メモリー容量",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.podCapacitySeriesLabel": "ポッド容量",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.sectionLabel": "Kubernetes概要",
     "xpack.infra.metricDetailPage.nginxMetricsLayout.activeConnectionsSection.sectionLabel": "アクティブな接続",
     "xpack.infra.metricDetailPage.nginxMetricsLayout.hitsSection.sectionLabel": "ヒット数",
     "xpack.infra.metricDetailPage.nginxMetricsLayout.requestRateSection.sectionLabel": "リクエストレート",
@@ -39839,4 +39794,4 @@
     "xpack.painlessLab.walkthroughButtonLabel": "実地検証",
     "xpack.serverlessObservability.nav.getStarted": "使ってみる"
   }
-}
+}
\ No newline at end of file
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 68e8ae1196d60..8474d0289c239 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -19010,25 +19010,6 @@
     "xpack.infra.metadataEmbeddable.setFilterByValueTooltip": "按值筛选",
     "xpack.infra.metadataEmbeddable.setRemoveFilterTooltip": "移除筛选",
     "xpack.infra.metadataEmbeddable.value": "值",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.cpuUtilSection.percentSeriesLabel": "百分比",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.cpuUtilSection.sectionLabel": "CPU 使用率",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.readsSeriesLabel": "读取数",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.sectionLabel": "磁盘 I/O 字节数",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.diskioBytesSection.writesSeriesLabel": "写入数",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.readsSeriesLabel": "读取数",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.sectionLabel": "磁盘 I/O 操作数",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.diskioOperationsSection.writesSeriesLabel": "写入数",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.rxSeriesLabel": "于",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.sectionLabel": "网络流量",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.networkBytesSection.txSeriesLabel": "传出",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.packetsInSeriesLabel": "于",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.packetsOutSeriesLabel": "传出",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.networkPacketsSection.sectionLabel": "网络数据包(平均值)",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.cpuUtilizationSeriesLabel": "CPU 使用率",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.networkPacketsInLabel": "数据包(传入)",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.networkPacketsOutLabel": "数据包(传出)",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.sectionLabel": "AWS 概览",
-    "xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.statusCheckFailedLabel": "状态检查失败",
     "xpack.infra.metricDetailPage.containerMetricsLayout.cpuUsageSection.sectionLabel": "CPU 使用率",
     "xpack.infra.metricDetailPage.containerMetricsLayout.diskIoBytesSection.readRateSeriesLabel": "读取数",
     "xpack.infra.metricDetailPage.containerMetricsLayout.diskIoBytesSection.sectionLabel": "磁盘 IO(字节)",
@@ -19053,32 +19034,6 @@
     "xpack.infra.metricDetailPage.ec2MetricsLayout.diskIOBytesSection.writeLabel": "写入数",
     "xpack.infra.metricDetailPage.ec2MetricsLayout.networkTrafficSection.sectionLabel": "网络流量",
     "xpack.infra.metricDetailPage.ec2MetricsLayout.overviewSection.sectionLabel": "Aws EC2 概览",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.cpuUsageSection.sectionLabel": "CPU 使用率",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.layoutLabel": "主机",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.fifteenMinuteSeriesLabel": "15 分钟",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.fiveMinuteSeriesLabel": "5 分钟",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.oneMinuteSeriesLabel": "1 分钟",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.loadSection.sectionLabel": "加载",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.memoryUsageSection.sectionLabel": "内存利用率",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.networkRxRateSeriesLabel": "于",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.networkTxRateSeriesLabel": "传出",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.networkTrafficSection.sectionLabel": "网络流量",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.cpuUsageSeriesLabel": "CPU 使用率",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.inboundRXSeriesLabel": "入站 (RX)",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.loadSeriesLabel": "负载(5 分钟)",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.memoryCapacitySeriesLabel": "内存利用率",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.outboundTXSeriesLabel": "出站 (TX)",
-    "xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.sectionLabel": "主机概览",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeCpuCapacitySection.sectionLabel": "节点 CPU 容量",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeDiskCapacitySection.sectionLabel": "节点磁盘容量",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodeMemoryCapacitySection.sectionLabel": "节点内存容量",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.nodePodCapacitySection.sectionLabel": "节点 Pod 容量",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.cpuUsageSeriesLabel": "CPU 容量",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.diskCapacitySeriesLabel": "磁盘容量",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.loadSeriesLabel": "负载(5 分钟)",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.memoryUsageSeriesLabel": "内存容量",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.podCapacitySeriesLabel": "Pod 容量",
-    "xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.sectionLabel": "Kubernetes 概览",
     "xpack.infra.metricDetailPage.nginxMetricsLayout.activeConnectionsSection.sectionLabel": "活动连接",
     "xpack.infra.metricDetailPage.nginxMetricsLayout.hitsSection.sectionLabel": "命中数",
     "xpack.infra.metricDetailPage.nginxMetricsLayout.requestRateSection.sectionLabel": "请求速率",
@@ -39833,4 +39788,4 @@
     "xpack.painlessLab.walkthroughButtonLabel": "指导",
     "xpack.serverlessObservability.nav.getStarted": "开始使用"
   }
-}
+}
\ No newline at end of file
diff --git a/x-pack/test/api_integration/apis/metrics_ui/metrics.ts b/x-pack/test/api_integration/apis/metrics_ui/metrics.ts
index 5e0cff513002e..a1e4552df68fe 100644
--- a/x-pack/test/api_integration/apis/metrics_ui/metrics.ts
+++ b/x-pack/test/api_integration/apis/metrics_ui/metrics.ts
@@ -15,7 +15,7 @@ import { FtrProviderContext } from '../../ftr_provider_context';
 
 import { DATES } from './constants';
 
-const { min, max } = DATES['7.0.0'].hosts;
+const { min, max } = DATES['8.0.0'].pods_only;
 
 interface NodeDetailsRequest {
   metrics: InventoryMetric[];
@@ -31,8 +31,8 @@ export default function ({ getService }: FtrProviderContext) {
   const supertest = getService('supertest');
 
   describe('metrics', () => {
-    before(() => esArchiver.load('x-pack/test/functional/es_archives/infra/7.0.0/hosts'));
-    after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/7.0.0/hosts'));
+    before(() => esArchiver.load('x-pack/test/functional/es_archives/infra/8.0.0/pods_only'));
+    after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/8.0.0/pods_only'));
 
     const fetchNodeDetails = async (
       body: NodeDetailsRequest
@@ -48,14 +48,14 @@ export default function ({ getService }: FtrProviderContext) {
     it('should basically work', async () => {
       const data = fetchNodeDetails({
         sourceId: 'default',
-        metrics: ['hostCpuUsage'],
+        metrics: ['podCpuUsage'],
         timerange: {
           to: max,
           from: min,
           interval: '>=1m',
         },
-        nodeId: 'demo-stack-mysql-01',
-        nodeType: 'host',
+        nodeId: '7d6d7955-f853-42b1-8613-11f52d0d2725',
+        nodeType: 'pod',
       });
       return data.then((resp) => {
         if (!resp) {
@@ -63,28 +63,28 @@ export default function ({ getService }: FtrProviderContext) {
         }
         expect(resp.metrics.length).to.equal(1);
         const metric = first(resp.metrics) as any;
-        expect(metric).to.have.property('id', 'hostCpuUsage');
+        expect(metric).to.have.property('id', 'podCpuUsage');
         expect(metric).to.have.property('series');
         const series = first(metric.series) as any;
-        expect(series).to.have.property('id', 'user');
+        expect(series).to.have.property('id', 'cpu');
         expect(series).to.have.property('data');
         const datapoint = last(series.data) as any;
-        expect(datapoint).to.have.property('timestamp', 1547571780000);
-        expect(datapoint).to.have.property('value', 0.0015);
+        expect(datapoint).to.have.property('timestamp', 1642698890000);
+        expect(datapoint).to.have.property('value', 0.544);
       });
     });
 
     it('should support multiple metrics', async () => {
       const data = fetchNodeDetails({
         sourceId: 'default',
-        metrics: ['hostCpuUsage', 'hostLoad'],
+        metrics: ['podCpuUsage', 'podMemoryUsage'],
         timerange: {
           to: max,
           from: min,
           interval: '>=1m',
         },
-        nodeId: 'demo-stack-mysql-01',
-        nodeType: 'host',
+        nodeId: '7d6d7955-f853-42b1-8613-11f52d0d2725',
+        nodeType: 'pod',
       });
       return data.then((resp) => {
         if (!resp) {
@@ -95,28 +95,26 @@ export default function ({ getService }: FtrProviderContext) {
       });
     });
 
-    it('should return multiple values for hostSystemOverview metric', () => {
+    it('should return multiple values for podOverview metric', () => {
       const data = fetchNodeDetails({
         sourceId: 'default',
-        metrics: ['hostSystemOverview'],
+        metrics: ['podOverview'],
         timerange: {
           to: max,
           from: min,
           interval: '>=1m',
         },
-        nodeId: 'demo-stack-mysql-01',
-        nodeType: 'host',
+        nodeId: '7d6d7955-f853-42b1-8613-11f52d0d2725',
+        nodeType: 'pod',
       });
       return data.then((resp) => {
         if (!resp) {
           return;
         }
 
-        const hostSystemOverviewMetric = resp.metrics.find(
-          (metric) => metric.id === 'hostSystemOverview'
-        );
+        const podOverviewMetric = resp.metrics.find((metric) => metric.id === 'podOverview');
 
-        expect(hostSystemOverviewMetric?.series.length).to.be.greaterThan(1);
+        expect(podOverviewMetric?.series.length).to.be.greaterThan(1);
       });
     });
   });

From b90b2114a04c40b0ee5ebe6c533402309e9af60f Mon Sep 17 00:00:00 2001
From: Thomas Watson <watson@elastic.co>
Date: Thu, 28 Sep 2023 13:17:39 +0200
Subject: [PATCH 08/20] Fix TypeScript issue in x-pack/test (#167488)

Follow up to #167343

See:
https://github.com/elastic/kibana/pull/167343#discussion_r1339688385
---
 x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts b/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts
index 3f5c414f46ef8..831659929d86d 100644
--- a/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts
+++ b/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts
@@ -10,6 +10,7 @@ import {
   X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
 } from '@kbn/core-http-common';
 import expect from '@kbn/expect';
+import type { GetAgentsResponse } from '@kbn/fleet-plugin/common';
 import { FtrProviderContext } from '../../api_integration/ftr_provider_context';
 import { skipIfNoDockerRegistry, generateAgent } from '../helpers';
 import { setupFleetAndAgents } from './agents/services';
@@ -130,7 +131,7 @@ export default function (providerContext: FtrProviderContext) {
       expectedAgentCount: number,
       attempts: number,
       _attemptsMade = 0
-    ): Promise<any> {
+    ): Promise<GetAgentsResponse> {
       const { body: apiResponse } = await supertest
         .get(`/api/fleet/agents?showInactive=true`)
         .set('kbn-xsrf', 'xxxx')

From 92a92fff674d3574cc1f374ce1b0fce4ea383b50 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Loix?= <sebastien.loix@elastic.co>
Date: Thu, 28 Sep 2023 12:20:53 +0100
Subject: [PATCH 09/20] [Cloud] ES endpoint discovery (#167122)

---
 .github/CODEOWNERS                            |   1 +
 .i18nrc.json                                  |   1 +
 docs/setup/connect-to-elasticsearch.asciidoc  |   3 +-
 package.json                                  |   1 +
 packages/cloud/README.md                      |   3 +
 .../deployment_details/deployment_details.tsx |  81 ++++++++++++
 .../deployment_details_cloudid_input.tsx      |  46 +++++++
 .../deployment_details_es_input.tsx           |  48 +++++++
 .../deployment_details_modal.tsx              |  69 ++++++++++
 packages/cloud/deployment_details/index.ts    |  11 ++
 .../cloud/deployment_details/services.tsx     | 123 ++++++++++++++++++
 packages/cloud/jest.config.js                 |  13 ++
 packages/cloud/kibana.jsonc                   |   5 +
 packages/cloud/package.json                   |   6 +
 packages/cloud/tsconfig.json                  |  21 +++
 .../src/ui/header/header_help_menu.tsx        |  46 +++++--
 .../core-chrome-browser/src/nav_controls.ts   |   4 +-
 tsconfig.base.json                            |   2 +
 .../cloud_links/kibana.jsonc                  |   3 +
 .../maybe_add_cloud_links/endpoints_modal.tsx |  31 +++++
 ...help_menu_links.ts => help_menu_links.tsx} |  36 +++++
 .../maybe_add_cloud_links.test.ts             |  17 +++
 .../maybe_add_cloud_links.ts                  |   7 +-
 .../cloud_links/public/plugin.test.ts         |  35 +++--
 .../cloud_links/public/plugin.tsx             |   6 +-
 .../cloud_links/tsconfig.json                 |   3 +
 .../header/deployment_details.component.tsx   | 102 ---------------
 .../header/deployment_details.stories.tsx     |  55 --------
 .../components/header/deployment_details.tsx  |  47 +++++--
 x-pack/plugins/fleet/tsconfig.json            |   1 +
 .../translations/translations/fr-FR.json      |   2 -
 .../translations/translations/ja-JP.json      |   2 -
 .../translations/translations/zh-CN.json      |   2 -
 x-pack/test/functional_cloud/config.ts        |   3 +-
 .../functional_cloud/tests/cloud_links.ts     |  22 ++++
 yarn.lock                                     |   4 +
 36 files changed, 663 insertions(+), 199 deletions(-)
 create mode 100644 packages/cloud/README.md
 create mode 100644 packages/cloud/deployment_details/deployment_details.tsx
 create mode 100644 packages/cloud/deployment_details/deployment_details_cloudid_input.tsx
 create mode 100644 packages/cloud/deployment_details/deployment_details_es_input.tsx
 create mode 100644 packages/cloud/deployment_details/deployment_details_modal.tsx
 create mode 100644 packages/cloud/deployment_details/index.ts
 create mode 100644 packages/cloud/deployment_details/services.tsx
 create mode 100644 packages/cloud/jest.config.js
 create mode 100644 packages/cloud/kibana.jsonc
 create mode 100644 packages/cloud/package.json
 create mode 100644 packages/cloud/tsconfig.json
 create mode 100644 x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/endpoints_modal.tsx
 rename x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/{help_menu_links.ts => help_menu_links.tsx} (53%)
 delete mode 100644 x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.component.tsx
 delete mode 100644 x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.stories.tsx

diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index be401c26dc0d2..89f769c0f12ec 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -67,6 +67,7 @@ packages/kbn-ci-stats-performance-metrics @elastic/kibana-operations
 packages/kbn-ci-stats-reporter @elastic/kibana-operations
 packages/kbn-ci-stats-shipper-cli @elastic/kibana-operations
 packages/kbn-cli-dev-mode @elastic/kibana-operations
+packages/cloud @elastic/kibana-core
 x-pack/plugins/cloud_integrations/cloud_chat @elastic/kibana-core
 x-pack/plugins/cloud_integrations/cloud_chat_provider @elastic/kibana-core
 x-pack/plugins/cloud_integrations/cloud_data_migration @elastic/platform-onboarding
diff --git a/.i18nrc.json b/.i18nrc.json
index b5e17c18d3542..4657840019f6c 100644
--- a/.i18nrc.json
+++ b/.i18nrc.json
@@ -15,6 +15,7 @@
     "customIntegrations": "src/plugins/custom_integrations",
     "customIntegrationsPackage": "packages/kbn-custom-integrations",
     "dashboard": "src/plugins/dashboard",
+    "cloud": "packages/cloud",
     "domDragDrop": "packages/kbn-dom-drag-drop",
     "controls": "src/plugins/controls",
     "data": "src/plugins/data",
diff --git a/docs/setup/connect-to-elasticsearch.asciidoc b/docs/setup/connect-to-elasticsearch.asciidoc
index e271eb6cce5c0..fef9ae71a085b 100644
--- a/docs/setup/connect-to-elasticsearch.asciidoc
+++ b/docs/setup/connect-to-elasticsearch.asciidoc
@@ -54,8 +54,9 @@ Details for each programming language library that Elastic provides are in the
 https://www.elastic.co/guide/en/elasticsearch/client/index.html[{es} Client documentation].
 
 If you are running {kib} on our hosted {es} Service,
-click *View deployment details* on the *Integrations* view
+click *Endpoints* on the *Integrations* view
 to verify your {es} endpoint and Cloud ID, and create API keys for integration.
+Alternatively, the *Endpoints* are also accessible through the top bar help menu.
 
 [float]
 === Add sample data
diff --git a/package.json b/package.json
index 3b7f8c030fc8f..313ee86a8171d 100644
--- a/package.json
+++ b/package.json
@@ -173,6 +173,7 @@
     "@kbn/chart-expressions-common": "link:src/plugins/chart_expressions/common",
     "@kbn/chart-icons": "link:packages/kbn-chart-icons",
     "@kbn/charts-plugin": "link:src/plugins/charts",
+    "@kbn/cloud": "link:packages/cloud",
     "@kbn/cloud-chat-plugin": "link:x-pack/plugins/cloud_integrations/cloud_chat",
     "@kbn/cloud-chat-provider-plugin": "link:x-pack/plugins/cloud_integrations/cloud_chat_provider",
     "@kbn/cloud-data-migration-plugin": "link:x-pack/plugins/cloud_integrations/cloud_data_migration",
diff --git a/packages/cloud/README.md b/packages/cloud/README.md
new file mode 100644
index 0000000000000..e387c4b9be959
--- /dev/null
+++ b/packages/cloud/README.md
@@ -0,0 +1,3 @@
+# @kbn/cloud
+
+Empty package generated by @kbn/generate
diff --git a/packages/cloud/deployment_details/deployment_details.tsx b/packages/cloud/deployment_details/deployment_details.tsx
new file mode 100644
index 0000000000000..278709f7b6d32
--- /dev/null
+++ b/packages/cloud/deployment_details/deployment_details.tsx
@@ -0,0 +1,81 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import React from 'react';
+
+import {
+  EuiForm,
+  EuiFlexGroup,
+  EuiFlexItem,
+  EuiLink,
+  EuiButtonEmpty,
+  EuiSpacer,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { useDeploymentDetails } from './services';
+import { DeploymentDetailsEsInput } from './deployment_details_es_input';
+import { DeploymentDetailsCloudIdInput } from './deployment_details_cloudid_input';
+
+const hasActiveModifierKey = (event: React.MouseEvent): boolean => {
+  return event.metaKey || event.altKey || event.ctrlKey || event.shiftKey;
+};
+
+export const DeploymentDetails = ({ closeModal }: { closeModal?: () => void }) => {
+  const { cloudId, elasticsearchUrl, managementUrl, learnMoreUrl, navigateToUrl } =
+    useDeploymentDetails();
+  const isInsideModal = !!closeModal;
+
+  if (!cloudId) {
+    return null;
+  }
+
+  return (
+    <EuiForm component="div">
+      {/* Elastic endpoint */}
+      {elasticsearchUrl && <DeploymentDetailsEsInput elasticsearchUrl={elasticsearchUrl} />}
+
+      {/* Cloud ID */}
+      <DeploymentDetailsCloudIdInput cloudId={cloudId} />
+
+      <EuiSpacer size="m" />
+
+      {managementUrl && (
+        <EuiFlexGroup gutterSize="m" justifyContent="spaceBetween" alignItems="center">
+          <EuiFlexItem grow={false}>
+            {/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
+            <EuiButtonEmpty
+              href={managementUrl}
+              onClick={(e: React.MouseEvent) => {
+                if (!hasActiveModifierKey(e)) {
+                  e.preventDefault();
+                  navigateToUrl(managementUrl);
+                }
+                if (closeModal) {
+                  closeModal();
+                }
+              }}
+              flush="left"
+            >
+              {i18n.translate('cloud.deploymentDetails.createManageApiKeysButtonLabel', {
+                defaultMessage: 'Create and manage API keys',
+              })}
+            </EuiButtonEmpty>
+          </EuiFlexItem>
+          {!isInsideModal && (
+            <EuiFlexItem grow={false}>
+              <EuiLink external href={learnMoreUrl} target="_blank">
+                {i18n.translate('cloud.deploymentDetails.learnMoreButtonLabel', {
+                  defaultMessage: 'Learn more',
+                })}
+              </EuiLink>
+            </EuiFlexItem>
+          )}
+        </EuiFlexGroup>
+      )}
+    </EuiForm>
+  );
+};
diff --git a/packages/cloud/deployment_details/deployment_details_cloudid_input.tsx b/packages/cloud/deployment_details/deployment_details_cloudid_input.tsx
new file mode 100644
index 0000000000000..a749fe4371715
--- /dev/null
+++ b/packages/cloud/deployment_details/deployment_details_cloudid_input.tsx
@@ -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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import React, { type FC } from 'react';
+import {
+  EuiFormRow,
+  EuiFieldText,
+  EuiCopy,
+  EuiButtonIcon,
+  EuiFlexGroup,
+  EuiFlexItem,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
+export const DeploymentDetailsCloudIdInput: FC<{ cloudId: string }> = ({ cloudId }) => {
+  return (
+    <EuiFormRow
+      label={i18n.translate('cloud.deploymentDetails.cloudIDLabel', {
+        defaultMessage: 'Cloud ID',
+      })}
+      fullWidth
+    >
+      <EuiFlexGroup gutterSize="s">
+        <EuiFlexItem>
+          <EuiFieldText
+            value={cloudId}
+            fullWidth
+            disabled
+            data-test-subj="deploymentDetailsCloudID"
+          />
+        </EuiFlexItem>
+        <EuiFlexItem grow={false}>
+          <EuiCopy textToCopy={cloudId}>
+            {(copy) => (
+              <EuiButtonIcon onClick={copy} iconType="copyClipboard" display="base" size="m" />
+            )}
+          </EuiCopy>
+        </EuiFlexItem>
+      </EuiFlexGroup>
+    </EuiFormRow>
+  );
+};
diff --git a/packages/cloud/deployment_details/deployment_details_es_input.tsx b/packages/cloud/deployment_details/deployment_details_es_input.tsx
new file mode 100644
index 0000000000000..2998b5bade543
--- /dev/null
+++ b/packages/cloud/deployment_details/deployment_details_es_input.tsx
@@ -0,0 +1,48 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import React, { type FC } from 'react';
+import {
+  EuiFormRow,
+  EuiFieldText,
+  EuiCopy,
+  EuiButtonIcon,
+  EuiFlexGroup,
+  EuiFlexItem,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
+export const DeploymentDetailsEsInput: FC<{ elasticsearchUrl: string }> = ({
+  elasticsearchUrl,
+}) => {
+  return (
+    <EuiFormRow
+      label={i18n.translate('cloud.deploymentDetails.elasticEndpointLabel', {
+        defaultMessage: 'Elastic endpoint',
+      })}
+      fullWidth
+    >
+      <EuiFlexGroup gutterSize="s">
+        <EuiFlexItem>
+          <EuiFieldText
+            value={elasticsearchUrl}
+            fullWidth
+            disabled
+            data-test-subj="deploymentDetailsEsEndpoint"
+          />
+        </EuiFlexItem>
+        <EuiFlexItem grow={false}>
+          <EuiCopy textToCopy={elasticsearchUrl}>
+            {(copy) => (
+              <EuiButtonIcon onClick={copy} iconType="copyClipboard" display="base" size="m" />
+            )}
+          </EuiCopy>
+        </EuiFlexItem>
+      </EuiFlexGroup>
+    </EuiFormRow>
+  );
+};
diff --git a/packages/cloud/deployment_details/deployment_details_modal.tsx b/packages/cloud/deployment_details/deployment_details_modal.tsx
new file mode 100644
index 0000000000000..2f3d628c2ca47
--- /dev/null
+++ b/packages/cloud/deployment_details/deployment_details_modal.tsx
@@ -0,0 +1,69 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import React, { type FC } from 'react';
+import { i18n } from '@kbn/i18n';
+import {
+  EuiButton,
+  EuiFlexGroup,
+  EuiFlexItem,
+  EuiLink,
+  EuiModal,
+  EuiModalBody,
+  EuiModalFooter,
+  EuiModalHeader,
+  EuiModalHeaderTitle,
+} from '@elastic/eui';
+import { useDeploymentDetails } from './services';
+import { DeploymentDetails } from './deployment_details';
+
+interface Props {
+  closeModal: () => void;
+}
+
+export const DeploymentDetailsModal: FC<Props> = ({ closeModal }) => {
+  const { learnMoreUrl } = useDeploymentDetails();
+
+  return (
+    <EuiModal
+      onClose={() => {
+        closeModal();
+      }}
+      style={{ width: 600 }}
+      data-test-subj="deploymentDetailsModal"
+    >
+      <EuiModalHeader>
+        <EuiModalHeaderTitle>
+          {i18n.translate('cloud.deploymentDetails.helpMenuLinks.endpoints', {
+            defaultMessage: 'Endpoints',
+          })}
+        </EuiModalHeaderTitle>
+      </EuiModalHeader>
+      <EuiModalBody>
+        <DeploymentDetails closeModal={closeModal} />
+      </EuiModalBody>
+      <EuiModalFooter>
+        <EuiFlexGroup alignItems="baseline" justifyContent="flexEnd">
+          <EuiFlexItem grow={false}>
+            <EuiLink external href={learnMoreUrl} target="_blank">
+              {i18n.translate('cloud.deploymentDetails.modal.learnMoreButtonLabel', {
+                defaultMessage: 'Learn more',
+              })}
+            </EuiLink>
+          </EuiFlexItem>
+          <EuiFlexItem grow={false}>
+            <EuiButton onClick={closeModal} fill>
+              {i18n.translate('cloud.deploymentDetails.modal.closeButtonLabel', {
+                defaultMessage: 'Close',
+              })}
+            </EuiButton>
+          </EuiFlexItem>
+        </EuiFlexGroup>
+      </EuiModalFooter>
+    </EuiModal>
+  );
+};
diff --git a/packages/cloud/deployment_details/index.ts b/packages/cloud/deployment_details/index.ts
new file mode 100644
index 0000000000000..2f37291eecd7c
--- /dev/null
+++ b/packages/cloud/deployment_details/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 { DeploymentDetailsKibanaProvider, DeploymentDetailsProvider } from './services';
+export { DeploymentDetails } from './deployment_details';
+export { DeploymentDetailsModal } from './deployment_details_modal';
diff --git a/packages/cloud/deployment_details/services.tsx b/packages/cloud/deployment_details/services.tsx
new file mode 100644
index 0000000000000..c4e8be12bb547
--- /dev/null
+++ b/packages/cloud/deployment_details/services.tsx
@@ -0,0 +1,123 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { FC, useContext } from 'react';
+
+export interface DeploymentDetailsContextValue {
+  cloudId?: string;
+  elasticsearchUrl?: string;
+  managementUrl?: string;
+  learnMoreUrl: string;
+  navigateToUrl(url: string): Promise<void>;
+}
+
+const DeploymentDetailsContext = React.createContext<DeploymentDetailsContextValue | null>(null);
+
+/**
+ * Abstract external service Provider.
+ */
+export const DeploymentDetailsProvider: FC<DeploymentDetailsContextValue> = ({
+  children,
+  ...services
+}) => {
+  return (
+    <DeploymentDetailsContext.Provider value={services}>
+      {children}
+    </DeploymentDetailsContext.Provider>
+  );
+};
+
+/**
+ * Kibana-specific service types.
+ */
+export interface DeploymentDetailsKibanaDependencies {
+  /** CoreStart contract */
+  core: {
+    application: {
+      navigateToUrl(url: string): Promise<void>;
+    };
+  };
+  /** SharePluginStart contract */
+  share: {
+    url: {
+      locators: {
+        get(
+          id: string
+        ): undefined | { useUrl: (params: { sectionId: string; appId: string }) => string };
+      };
+    };
+  };
+  /** CloudSetup contract */
+  cloud: {
+    isCloudEnabled: boolean;
+    cloudId?: string;
+    elasticsearchUrl?: string;
+  };
+  /** DocLinksStart contract */
+  docLinks: {
+    links: {
+      fleet: {
+        apiKeysLearnMore: string;
+      };
+    };
+  };
+}
+
+/**
+ * Kibana-specific Provider that maps to known dependency types.
+ */
+export const DeploymentDetailsKibanaProvider: FC<DeploymentDetailsKibanaDependencies> = ({
+  children,
+  ...services
+}) => {
+  const {
+    core: {
+      application: { navigateToUrl },
+    },
+    cloud: { isCloudEnabled, cloudId, elasticsearchUrl },
+    share: {
+      url: { locators },
+    },
+    docLinks: {
+      links: {
+        fleet: { apiKeysLearnMore },
+      },
+    },
+  } = services;
+
+  const managementUrl = locators
+    .get('MANAGEMENT_APP_LOCATOR')
+    ?.useUrl({ sectionId: 'security', appId: 'api_keys' });
+
+  return (
+    <DeploymentDetailsProvider
+      cloudId={isCloudEnabled ? cloudId : undefined}
+      elasticsearchUrl={elasticsearchUrl}
+      managementUrl={managementUrl}
+      learnMoreUrl={apiKeysLearnMore}
+      navigateToUrl={navigateToUrl}
+    >
+      {children}
+    </DeploymentDetailsProvider>
+  );
+};
+
+/**
+ * React hook for accessing pre-wired services.
+ */
+export function useDeploymentDetails() {
+  const context = useContext(DeploymentDetailsContext);
+
+  if (!context) {
+    throw new Error(
+      'DeploymentDetailsContext is missing. Ensure your component or React root is wrapped with <DeploymentDetailsProvider /> or <DeploymentDetailsKibanaProvider />.'
+    );
+  }
+
+  return context;
+}
diff --git a/packages/cloud/jest.config.js b/packages/cloud/jest.config.js
new file mode 100644
index 0000000000000..174f01cfc1be6
--- /dev/null
+++ b/packages/cloud/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',
+  rootDir: '../..',
+  roots: ['<rootDir>/packages/cloud'],
+};
diff --git a/packages/cloud/kibana.jsonc b/packages/cloud/kibana.jsonc
new file mode 100644
index 0000000000000..e39a0dbe40617
--- /dev/null
+++ b/packages/cloud/kibana.jsonc
@@ -0,0 +1,5 @@
+{
+  "type": "shared-common",
+  "id": "@kbn/cloud",
+  "owner": "@elastic/kibana-core"
+}
diff --git a/packages/cloud/package.json b/packages/cloud/package.json
new file mode 100644
index 0000000000000..8e0023dc5c7a3
--- /dev/null
+++ b/packages/cloud/package.json
@@ -0,0 +1,6 @@
+{
+  "name": "@kbn/cloud",
+  "private": true,
+  "version": "1.0.0",
+  "license": "SSPL-1.0 OR Elastic License 2.0"
+}
\ No newline at end of file
diff --git a/packages/cloud/tsconfig.json b/packages/cloud/tsconfig.json
new file mode 100644
index 0000000000000..c4703bc51cf6c
--- /dev/null
+++ b/packages/cloud/tsconfig.json
@@ -0,0 +1,21 @@
+{
+  "extends": "../../tsconfig.base.json",
+  "compilerOptions": {
+    "outDir": "target/types",
+    "types": [
+      "jest",
+      "node",
+      "react"
+    ]
+  },
+  "include": [
+    "**/*.ts",
+    "**/*.tsx",
+  ],
+  "exclude": [
+    "target/**/*"
+  ],
+  "kbn_references": [
+    "@kbn/i18n",
+  ]
+}
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_help_menu.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_help_menu.tsx
index e1e43d43ab401..5c3d5bb048737 100644
--- a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_help_menu.tsx
+++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_help_menu.tsx
@@ -67,6 +67,7 @@ const buildDefaultContentLinks = ({
       defaultMessage: 'Open an issue in GitHub',
     }),
     href: docLinks.links.kibana.createGithubIssue,
+    iconType: 'logoGithub',
   },
 ];
 
@@ -201,17 +202,40 @@ export class HeaderHelpMenu extends Component<Props, State> {
 
     return (
       <Fragment>
-        {defaultContentLinks.map(({ href, title, iconType }, i) => {
-          const isLast = i === defaultContentLinks.length - 1;
-          return (
-            <Fragment key={i}>
-              <EuiButtonEmpty href={href} target="_blank" size="s" flush="left" iconType={iconType}>
-                {title}
-              </EuiButtonEmpty>
-              {!isLast && <EuiSpacer size="xs" />}
-            </Fragment>
-          );
-        })}
+        {defaultContentLinks.map(
+          ({ href, title, iconType, onClick: _onClick, dataTestSubj }, i) => {
+            const isLast = i === defaultContentLinks.length - 1;
+
+            if (href && _onClick) {
+              throw new Error(
+                'Only one of `href` and `onClick` should be provided for the help menu link.'
+              );
+            }
+
+            const hrefProps = href ? { href, target: '_blank' } : {};
+            const onClick = () => {
+              if (!_onClick) return;
+              _onClick();
+              this.closeMenu();
+            };
+
+            return (
+              <Fragment key={i}>
+                <EuiButtonEmpty
+                  {...hrefProps}
+                  onClick={onClick}
+                  size="s"
+                  flush="left"
+                  iconType={iconType}
+                  data-test-subj={dataTestSubj}
+                >
+                  {title}
+                </EuiButtonEmpty>
+                {!isLast && <EuiSpacer size="xs" />}
+              </Fragment>
+            );
+          }
+        )}
       </Fragment>
     );
   }
diff --git a/packages/core/chrome/core-chrome-browser/src/nav_controls.ts b/packages/core/chrome/core-chrome-browser/src/nav_controls.ts
index 39b5d1b3b59b1..22c074862151b 100644
--- a/packages/core/chrome/core-chrome-browser/src/nav_controls.ts
+++ b/packages/core/chrome/core-chrome-browser/src/nav_controls.ts
@@ -18,8 +18,10 @@ export interface ChromeNavControl {
 /** @public */
 export interface ChromeHelpMenuLink {
   title: string;
-  href: string;
+  href?: string;
   iconType?: string;
+  onClick?: () => void;
+  dataTestSubj?: string;
 }
 
 /**
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 030b5c9bbed4c..bb2aec9d5819f 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -128,6 +128,8 @@
       "@kbn/ci-stats-shipper-cli/*": ["packages/kbn-ci-stats-shipper-cli/*"],
       "@kbn/cli-dev-mode": ["packages/kbn-cli-dev-mode"],
       "@kbn/cli-dev-mode/*": ["packages/kbn-cli-dev-mode/*"],
+      "@kbn/cloud": ["packages/cloud"],
+      "@kbn/cloud/*": ["packages/cloud/*"],
       "@kbn/cloud-chat-plugin": ["x-pack/plugins/cloud_integrations/cloud_chat"],
       "@kbn/cloud-chat-plugin/*": ["x-pack/plugins/cloud_integrations/cloud_chat/*"],
       "@kbn/cloud-chat-provider-plugin": ["x-pack/plugins/cloud_integrations/cloud_chat_provider"],
diff --git a/x-pack/plugins/cloud_integrations/cloud_links/kibana.jsonc b/x-pack/plugins/cloud_integrations/cloud_links/kibana.jsonc
index 4b6625f842f79..660f6e64a2446 100644
--- a/x-pack/plugins/cloud_integrations/cloud_links/kibana.jsonc
+++ b/x-pack/plugins/cloud_integrations/cloud_links/kibana.jsonc
@@ -14,6 +14,9 @@
     ],
     "requiredBundles": [
       "kibanaReact"
+    ],
+    "requiredPlugins": [
+      "share"
     ]
   }
 }
diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/endpoints_modal.tsx b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/endpoints_modal.tsx
new file mode 100644
index 0000000000000..7c6b23d352f1a
--- /dev/null
+++ b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/endpoints_modal.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; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import React from 'react';
+import type { CoreStart } from '@kbn/core/public';
+import type { DocLinksStart } from '@kbn/core-doc-links-browser';
+import type { CloudStart } from '@kbn/cloud-plugin/public';
+import type { SharePluginStart } from '@kbn/share-plugin/public';
+import {
+  DeploymentDetailsKibanaProvider,
+  DeploymentDetailsModal,
+} from '@kbn/cloud/deployment_details';
+
+interface Props {
+  closeModal: () => void;
+  core: CoreStart;
+  docLinks: DocLinksStart;
+  cloud: CloudStart;
+  share: SharePluginStart;
+}
+
+export const EndpointsModal = ({ core, share, cloud, docLinks, closeModal }: Props) => {
+  return (
+    <DeploymentDetailsKibanaProvider core={core} share={share} cloud={cloud} docLinks={docLinks}>
+      <DeploymentDetailsModal closeModal={closeModal} />
+    </DeploymentDetailsKibanaProvider>
+  );
+};
diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/help_menu_links.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/help_menu_links.tsx
similarity index 53%
rename from x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/help_menu_links.ts
rename to x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/help_menu_links.tsx
index 82b0e86e6569a..15270c5876214 100644
--- a/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/help_menu_links.ts
+++ b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/help_menu_links.tsx
@@ -4,17 +4,32 @@
  * 2.0; you may not use this file except in compliance with the Elastic License
  * 2.0.
  */
+import React from 'react';
 import { i18n } from '@kbn/i18n';
 import { ChromeHelpMenuLink } from '@kbn/core-chrome-browser';
 import type { DocLinksStart } from '@kbn/core-doc-links-browser';
+import type { CoreStart } from '@kbn/core/public';
+import type { CloudStart } from '@kbn/cloud-plugin/public';
+import type { SharePluginStart } from '@kbn/share-plugin/public';
+import { toMountPoint } from '@kbn/react-kibana-mount';
+
+import { EndpointsModal } from './endpoints_modal';
 
 export const createHelpMenuLinks = ({
   docLinks,
   helpSupportUrl,
+  core,
+  cloud,
+  share,
 }: {
   docLinks: DocLinksStart;
+  core: CoreStart;
+  cloud: CloudStart;
+  share: SharePluginStart;
   helpSupportUrl: string;
 }) => {
+  const { overlays } = core;
+
   const helpMenuLinks: ChromeHelpMenuLink[] = [
     {
       title: i18n.translate('xpack.cloudLinks.helpMenuLinks.documentation', {
@@ -34,6 +49,27 @@ export const createHelpMenuLinks = ({
       }),
       href: docLinks.links.kibana.feedback,
     },
+    {
+      title: i18n.translate('xpack.cloudLinks.helpMenuLinks.endpoints', {
+        defaultMessage: 'Endpoints',
+      }),
+      iconType: 'console',
+      dataTestSubj: 'endpointsHelpLink',
+      onClick: () => {
+        const modal = overlays.openModal(
+          toMountPoint(
+            <EndpointsModal
+              core={core}
+              share={share}
+              cloud={cloud}
+              docLinks={docLinks}
+              closeModal={() => modal.close()}
+            />,
+            { theme: core.theme, i18n: core.i18n }
+          )
+        );
+      },
+    },
   ];
 
   return helpMenuLinks;
diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.test.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.test.ts
index b9045fdc9a59f..d680d6cce4f4f 100644
--- a/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.test.ts
+++ b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.test.ts
@@ -8,6 +8,7 @@
 import { cloudMock } from '@kbn/cloud-plugin/public/mocks';
 import { coreMock } from '@kbn/core/public/mocks';
 import { securityMock } from '@kbn/security-plugin/public/mocks';
+import { sharePluginMock } from '@kbn/share-plugin/public/mocks';
 
 import { maybeAddCloudLinks } from './maybe_add_cloud_links';
 
@@ -18,6 +19,7 @@ describe('maybeAddCloudLinks', () => {
     maybeAddCloudLinks({
       core,
       security,
+      share: sharePluginMock.createStartContract(),
       cloud: { ...cloudMock.createStart(), isCloudEnabled: false },
     });
     // Since there's a promise, let's wait for the next tick
@@ -35,6 +37,7 @@ describe('maybeAddCloudLinks', () => {
     maybeAddCloudLinks({
       security,
       core,
+      share: sharePluginMock.createStartContract(),
       cloud: { ...cloudMock.createStart(), isCloudEnabled: true },
     });
     // Since there's a promise, let's wait for the next tick
@@ -90,6 +93,12 @@ describe('maybeAddCloudLinks', () => {
             "href": "https://www.elastic.co/products/kibana/feedback?blade=kibanafeedback",
             "title": "Give feedback",
           },
+          Object {
+            "dataTestSubj": "endpointsHelpLink",
+            "iconType": "console",
+            "onClick": [Function],
+            "title": "Endpoints",
+          },
         ],
       ]
     `);
@@ -103,6 +112,7 @@ describe('maybeAddCloudLinks', () => {
     maybeAddCloudLinks({
       security,
       core,
+      share: sharePluginMock.createStartContract(),
       cloud: { ...cloudMock.createStart(), isCloudEnabled: true },
     });
     // Since there's a promise, let's wait for the next tick
@@ -157,6 +167,12 @@ describe('maybeAddCloudLinks', () => {
             "href": "https://www.elastic.co/products/kibana/feedback?blade=kibanafeedback",
             "title": "Give feedback",
           },
+          Object {
+            "dataTestSubj": "endpointsHelpLink",
+            "iconType": "console",
+            "onClick": [Function],
+            "title": "Endpoints",
+          },
         ],
       ]
     `);
@@ -172,6 +188,7 @@ describe('maybeAddCloudLinks', () => {
     maybeAddCloudLinks({
       security,
       core,
+      share: sharePluginMock.createStartContract(),
       cloud: { ...cloudMock.createStart(), isCloudEnabled: true },
     });
     // Since there's a promise, let's wait for the next tick
diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.ts
index 33fb4df7bfce2..2772c87d124d3 100644
--- a/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.ts
+++ b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.ts
@@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n';
 import type { CloudStart } from '@kbn/cloud-plugin/public';
 import type { CoreStart } from '@kbn/core/public';
 import type { SecurityPluginStart } from '@kbn/security-plugin/public';
+import type { SharePluginStart } from '@kbn/share-plugin/public';
 import { createUserMenuLinks } from './user_menu_links';
 import { createHelpMenuLinks } from './help_menu_links';
 
@@ -18,9 +19,10 @@ export interface MaybeAddCloudLinksDeps {
   core: CoreStart;
   security: SecurityPluginStart;
   cloud: CloudStart;
+  share: SharePluginStart;
 }
 
-export function maybeAddCloudLinks({ core, security, cloud }: MaybeAddCloudLinksDeps): void {
+export function maybeAddCloudLinks({ core, security, cloud, share }: MaybeAddCloudLinksDeps): void {
   const userObservable = defer(() => security.authc.getCurrentUser()).pipe(
     // Check if user is a cloud user.
     map((user) => user.elastic_cloud_user),
@@ -54,6 +56,9 @@ export function maybeAddCloudLinks({ core, security, cloud }: MaybeAddCloudLinks
         const helpMenuLinks = createHelpMenuLinks({
           docLinks: core.docLinks,
           helpSupportUrl,
+          core,
+          share,
+          cloud,
         });
 
         core.chrome.setHelpMenuLinks(helpMenuLinks);
diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts
index d928b7a6f0e8a..d2f987337a440 100644
--- a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts
+++ b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts
@@ -11,6 +11,7 @@ import { coreMock } from '@kbn/core/public/mocks';
 import { cloudMock } from '@kbn/cloud-plugin/public/mocks';
 import { securityMock } from '@kbn/security-plugin/public/mocks';
 import { guidedOnboardingMock } from '@kbn/guided-onboarding-plugin/public/mocks';
+import { sharePluginMock } from '@kbn/share-plugin/public/mocks';
 
 describe('Cloud Links Plugin - public', () => {
   let plugin: CloudLinksPlugin;
@@ -40,7 +41,11 @@ describe('Cloud Links Plugin - public', () => {
           coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false);
           const cloud = { ...cloudMock.createStart(), isCloudEnabled: true };
 
-          plugin.start(coreStart, { cloud, guidedOnboarding });
+          plugin.start(coreStart, {
+            cloud,
+            guidedOnboarding,
+            share: sharePluginMock.createStartContract(),
+          });
           expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).toHaveBeenCalledTimes(1);
         });
 
@@ -48,14 +53,22 @@ describe('Cloud Links Plugin - public', () => {
           const coreStart = coreMock.createStart();
           coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true);
           const cloud = { ...cloudMock.createStart(), isCloudEnabled: true };
-          plugin.start(coreStart, { cloud, guidedOnboarding });
+          plugin.start(coreStart, {
+            cloud,
+            guidedOnboarding,
+            share: sharePluginMock.createStartContract(),
+          });
           expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).not.toHaveBeenCalled();
         });
 
         test('does not register the Onboarding Setup Guide link when cloud is not enabled', () => {
           const coreStart = coreMock.createStart();
           const cloud = { ...cloudMock.createStart(), isCloudEnabled: false };
-          plugin.start(coreStart, { cloud, guidedOnboarding });
+          plugin.start(coreStart, {
+            cloud,
+            guidedOnboarding,
+            share: sharePluginMock.createStartContract(),
+          });
           expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).not.toHaveBeenCalled();
         });
       });
@@ -72,7 +85,11 @@ describe('Cloud Links Plugin - public', () => {
         coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false);
         const cloud = { ...cloudMock.createStart(), isCloudEnabled: true };
 
-        plugin.start(coreStart, { cloud, guidedOnboarding });
+        plugin.start(coreStart, {
+          cloud,
+          guidedOnboarding,
+          share: sharePluginMock.createStartContract(),
+        });
         expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).not.toHaveBeenCalled();
       });
     });
@@ -83,7 +100,7 @@ describe('Cloud Links Plugin - public', () => {
         coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false);
         const cloud = { ...cloudMock.createStart(), isCloudEnabled: true };
         const security = securityMock.createStart();
-        plugin.start(coreStart, { cloud, security });
+        plugin.start(coreStart, { cloud, security, share: sharePluginMock.createStartContract() });
         expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(1);
       });
 
@@ -91,7 +108,7 @@ describe('Cloud Links Plugin - public', () => {
         const coreStart = coreMock.createStart();
         coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false);
         const cloud = { ...cloudMock.createStart(), isCloudEnabled: true };
-        plugin.start(coreStart, { cloud });
+        plugin.start(coreStart, { cloud, share: sharePluginMock.createStartContract() });
         expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0);
       });
 
@@ -100,7 +117,7 @@ describe('Cloud Links Plugin - public', () => {
         coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true);
         const cloud = { ...cloudMock.createStart(), isCloudEnabled: true };
         const security = securityMock.createStart();
-        plugin.start(coreStart, { cloud, security });
+        plugin.start(coreStart, { cloud, security, share: sharePluginMock.createStartContract() });
         expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0);
       });
 
@@ -108,7 +125,7 @@ describe('Cloud Links Plugin - public', () => {
         const coreStart = coreMock.createStart();
         coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false);
         const security = securityMock.createStart();
-        plugin.start(coreStart, { security });
+        plugin.start(coreStart, { security, share: sharePluginMock.createStartContract() });
         expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0);
       });
 
@@ -117,7 +134,7 @@ describe('Cloud Links Plugin - public', () => {
         coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false);
         const cloud = { ...cloudMock.createStart(), isCloudEnabled: false };
         const security = securityMock.createStart();
-        plugin.start(coreStart, { cloud, security });
+        plugin.start(coreStart, { cloud, security, share: sharePluginMock.createStartContract() });
         expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0);
       });
     });
diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.tsx b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.tsx
index 38b568791b70b..bfebe531276d4 100755
--- a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.tsx
+++ b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.tsx
@@ -11,6 +11,7 @@ import type { CoreStart, Plugin } from '@kbn/core/public';
 import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public';
 import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public';
 import type { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public';
+import type { SharePluginStart } from '@kbn/share-plugin/public';
 import { maybeAddCloudLinks } from './maybe_add_cloud_links';
 
 interface CloudLinksDepsSetup {
@@ -21,6 +22,7 @@ interface CloudLinksDepsSetup {
 interface CloudLinksDepsStart {
   cloud?: CloudStart;
   security?: SecurityPluginStart;
+  share: SharePluginStart;
   guidedOnboarding?: GuidedOnboardingPluginStart;
 }
 
@@ -29,7 +31,7 @@ export class CloudLinksPlugin
 {
   public setup() {}
 
-  public start(core: CoreStart, { cloud, security, guidedOnboarding }: CloudLinksDepsStart) {
+  public start(core: CoreStart, { cloud, security, guidedOnboarding, share }: CloudLinksDepsStart) {
     if (cloud?.isCloudEnabled && !core.http.anonymousPaths.isAnonymous(window.location.pathname)) {
       if (guidedOnboarding?.guidedOnboardingApi?.isEnabled) {
         core.chrome.registerGlobalHelpExtensionMenuLink({
@@ -42,11 +44,13 @@ export class CloudLinksPlugin
           priority: 1000, // We want this link to be at the very top.
         });
       }
+
       if (security) {
         maybeAddCloudLinks({
           core,
           security,
           cloud,
+          share,
         });
       }
     }
diff --git a/x-pack/plugins/cloud_integrations/cloud_links/tsconfig.json b/x-pack/plugins/cloud_integrations/cloud_links/tsconfig.json
index f1a67895cdd5e..43f411cadf060 100644
--- a/x-pack/plugins/cloud_integrations/cloud_links/tsconfig.json
+++ b/x-pack/plugins/cloud_integrations/cloud_links/tsconfig.json
@@ -23,6 +23,9 @@
     "@kbn/user-profile-components",
     "@kbn/core-lifecycle-browser",
     "@kbn/kibana-react-plugin",
+    "@kbn/share-plugin",
+    "@kbn/cloud",
+    "@kbn/react-kibana-mount",
   ],
   "exclude": [
     "target/**/*",
diff --git a/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.component.tsx b/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.component.tsx
deleted file mode 100644
index 7e3b414cf0f6d..0000000000000
--- a/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.component.tsx
+++ /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 React from 'react';
-import styled from 'styled-components';
-
-import {
-  EuiPopover,
-  EuiText,
-  EuiForm,
-  EuiFormRow,
-  EuiFieldText,
-  EuiCopy,
-  EuiButtonIcon,
-  EuiFlexGroup,
-  EuiFlexItem,
-  EuiButton,
-  EuiLink,
-  EuiHeaderLink,
-} from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-
-export interface Props {
-  cloudId: string;
-  managementUrl?: string;
-  learnMoreUrl: string;
-}
-
-const Description = styled(EuiText)`
-  margin-bottom: ${({ theme }) => theme.eui.euiSizeL};
-`;
-
-export const DeploymentDetails = ({ cloudId, learnMoreUrl, managementUrl }: Props) => {
-  const [isOpen, setIsOpen] = React.useState(false);
-
-  const button = (
-    <EuiHeaderLink onClick={() => setIsOpen(!isOpen)} iconType="iInCircle" iconSide="left" isActive>
-      {i18n.translate('xpack.fleet.integrations.deploymentButton', {
-        defaultMessage: 'View deployment details',
-      })}
-    </EuiHeaderLink>
-  );
-
-  const management = managementUrl ? (
-    <EuiFormRow label="API keys" fullWidth>
-      <EuiFlexGroup gutterSize="m" alignItems="center">
-        <EuiFlexItem>
-          <EuiButton href={managementUrl}>Create and manage API keys</EuiButton>
-        </EuiFlexItem>
-        <EuiFlexItem>
-          <EuiLink external href={learnMoreUrl} target="_blank">
-            Learn more
-          </EuiLink>
-        </EuiFlexItem>
-      </EuiFlexGroup>
-    </EuiFormRow>
-  ) : null;
-
-  return (
-    <EuiPopover
-      isOpen={isOpen}
-      closePopover={() => setIsOpen(false)}
-      button={button}
-      anchorPosition="downCenter"
-    >
-      <div style={{ width: 450 }}>
-        <Description>
-          {i18n.translate('xpack.fleet.integrations.deploymentDescription', {
-            defaultMessage:
-              'Send data to Elastic from your applications by referencing your deployment.',
-          })}
-        </Description>
-        <EuiForm component="div">
-          <EuiFormRow label="Cloud ID" fullWidth>
-            <EuiFlexGroup gutterSize="s">
-              <EuiFlexItem>
-                <EuiFieldText value={cloudId} fullWidth disabled />
-              </EuiFlexItem>
-              <EuiFlexItem grow={false}>
-                <EuiCopy textToCopy={cloudId}>
-                  {(copy) => (
-                    <EuiButtonIcon
-                      onClick={copy}
-                      iconType="copyClipboard"
-                      display="base"
-                      size="m"
-                    />
-                  )}
-                </EuiCopy>
-              </EuiFlexItem>
-            </EuiFlexGroup>
-          </EuiFormRow>
-          {management}
-        </EuiForm>
-      </div>
-    </EuiPopover>
-  );
-};
diff --git a/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.stories.tsx
deleted file mode 100644
index 5b311b3443e36..0000000000000
--- a/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.stories.tsx
+++ /dev/null
@@ -1,55 +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 type { Meta } from '@storybook/react';
-import { EuiHeader } from '@elastic/eui';
-
-import { DeploymentDetails as ConnectedComponent } from './deployment_details';
-import type { Props as PureComponentProps } from './deployment_details.component';
-import { DeploymentDetails as PureComponent } from './deployment_details.component';
-
-export default {
-  title: 'Sections/EPM/Deployment Details',
-  description: '',
-  decorators: [
-    (storyFn) => {
-      const sections = [{ items: [] }, { items: [storyFn()] }];
-      return <EuiHeader sections={sections} />;
-    },
-  ],
-} as Meta;
-
-export const DeploymentDetails = () => {
-  return <ConnectedComponent />;
-};
-
-DeploymentDetails.args = {
-  isCloudEnabled: true,
-};
-
-DeploymentDetails.argTypes = {
-  isCloudEnabled: {
-    type: {
-      name: 'boolean',
-    },
-    defaultValue: true,
-    control: {
-      type: 'boolean',
-    },
-  },
-};
-
-export const Component = (props: PureComponentProps) => {
-  return <PureComponent {...props} />;
-};
-
-Component.args = {
-  cloudId: 'cloud-id',
-  learnMoreUrl: 'https://learn-more-url',
-  managementUrl: 'https://management-url',
-};
diff --git a/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.tsx b/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.tsx
index 968d596a5f9d5..ea17bc3f201c4 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.tsx
@@ -6,13 +6,18 @@
  */
 
 import React from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiPopover, EuiHeaderLink } from '@elastic/eui';
+import {
+  DeploymentDetailsKibanaProvider,
+  DeploymentDetails as DeploymentDetailsComponent,
+} from '@kbn/cloud/deployment_details';
 
 import { useStartServices } from '../../hooks';
 
-import { DeploymentDetails as Component } from './deployment_details.component';
-
 export const DeploymentDetails = () => {
-  const { share, cloud, docLinks } = useStartServices();
+  const [isOpen, setIsOpen] = React.useState(false);
+  const { share, cloud, docLinks, application } = useStartServices();
 
   // If the cloud plugin isn't enabled, we can't display the flyout.
   if (!cloud) {
@@ -21,16 +26,36 @@ export const DeploymentDetails = () => {
 
   const { isCloudEnabled, cloudId } = cloud;
 
-  // If cloud isn't enabled or we don't have a cloudId we can't display the flyout.
+  // If cloud isn't enabled or we don't have a cloudId we don't render the button.
   if (!isCloudEnabled || !cloudId) {
     return null;
   }
 
-  const managementUrl = share.url.locators
-    .get('MANAGEMENT_APP_LOCATOR')
-    ?.useUrl({ sectionId: 'security', appId: 'api_keys' });
-
-  const learnMoreUrl = docLinks.links.fleet.apiKeysLearnMore;
-
-  return <Component {...{ cloudId, managementUrl, learnMoreUrl }} />;
+  const button = (
+    <EuiHeaderLink onClick={() => setIsOpen(!isOpen)} iconType="iInCircle" iconSide="left" isActive>
+      {i18n.translate('xpack.fleet.integrations.endpointsButton', {
+        defaultMessage: 'Endpoints',
+      })}
+    </EuiHeaderLink>
+  );
+
+  return (
+    <DeploymentDetailsKibanaProvider
+      core={{ application }}
+      share={share}
+      cloud={cloud}
+      docLinks={docLinks}
+    >
+      <EuiPopover
+        isOpen={isOpen}
+        closePopover={() => setIsOpen(false)}
+        button={button}
+        anchorPosition="downCenter"
+      >
+        <div style={{ width: 450 }}>
+          <DeploymentDetailsComponent />
+        </div>
+      </EuiPopover>
+    </DeploymentDetailsKibanaProvider>
+  );
 };
diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json
index b3f8a96417f9a..58cdfa25d1e08 100644
--- a/x-pack/plugins/fleet/tsconfig.json
+++ b/x-pack/plugins/fleet/tsconfig.json
@@ -101,5 +101,6 @@
     "@kbn/core-saved-objects-base-server-internal",
     "@kbn/core-http-common",
     "@kbn/dashboard-plugin",
+    "@kbn/cloud",
   ]
 }
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index b468f00d02466..4ae31ffb358ef 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -16425,8 +16425,6 @@
     "xpack.fleet.homeIntegration.tutorialModule.noticeText.notePrefix": "Note",
     "xpack.fleet.initializationErrorMessageTitle": "Initialisation de Fleet impossible",
     "xpack.fleet.integrations.customInputsLink": "entrées personnalisées",
-    "xpack.fleet.integrations.deploymentButton": "Voir les détails du déploiement",
-    "xpack.fleet.integrations.deploymentDescription": "Envoyez des données à Elastic à partir de vos applications en référençant votre déploiement.",
     "xpack.fleet.integrations.discussForumLink": "forum",
     "xpack.fleet.integrations.installPackage.uploadedTooltip": "Cette intégration a été installée par le biais d'un chargement et ne peut pas être réinstallée automatiquement. Veuillez la charger à nouveau pour la réinstaller.",
     "xpack.fleet.integrations.integrationSaved": "Paramètres de l'intégration enregistrés",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 7f59456c6bd14..87e23ff29cb1f 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -16439,8 +16439,6 @@
     "xpack.fleet.homeIntegration.tutorialModule.noticeText.notePrefix": "注",
     "xpack.fleet.initializationErrorMessageTitle": "Fleet を初期化できません",
     "xpack.fleet.integrations.customInputsLink": "カスタム入力",
-    "xpack.fleet.integrations.deploymentButton": "デプロイ詳細の表示",
-    "xpack.fleet.integrations.deploymentDescription": "デプロイを参照し、アプリケーションのデータをElasticに送信します。",
     "xpack.fleet.integrations.discussForumLink": "フォーラム",
     "xpack.fleet.integrations.installPackage.uploadedTooltip": "この統合はアップロードによってインストールされたため、自動的に再インストールできません。再インストールするには、もう一度アップロードしてください。",
     "xpack.fleet.integrations.integrationSaved": "統合設定が保存されました",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 8474d0289c239..6e3c128ec642a 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -16439,8 +16439,6 @@
     "xpack.fleet.homeIntegration.tutorialModule.noticeText.notePrefix": "备注",
     "xpack.fleet.initializationErrorMessageTitle": "无法初始化 Fleet",
     "xpack.fleet.integrations.customInputsLink": "定制输入",
-    "xpack.fleet.integrations.deploymentButton": "查看部署详情",
-    "xpack.fleet.integrations.deploymentDescription": "通过引用部署,将数据从应用程序发送到 Elastic。",
     "xpack.fleet.integrations.discussForumLink": "论坛",
     "xpack.fleet.integrations.installPackage.uploadedTooltip": "此集成通过上传进行安装,因此无法自动重新安装。请再次将其上传,以便重新安装。",
     "xpack.fleet.integrations.integrationSaved": "已保存集成设置",
diff --git a/x-pack/test/functional_cloud/config.ts b/x-pack/test/functional_cloud/config.ts
index c3203677631a9..df75e83138ed5 100644
--- a/x-pack/test/functional_cloud/config.ts
+++ b/x-pack/test/functional_cloud/config.ts
@@ -44,7 +44,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
       serverArgs: [
         ...functionalConfig.get('kbnTestServer.serverArgs'),
         `--plugin-path=${samlIdPPlugin}`,
-        '--xpack.cloud.id=ftr_fake_cloud_id',
+        // Note: the base64 string in the cloud.id config contains the ES endpoint required in the functional tests
+        '--xpack.cloud.id=ftr_fake_cloud_id:aGVsbG8uY29tOjQ0MyRFUzEyM2FiYyRrYm4xMjNhYmM=',
         '--xpack.cloud.base_url=https://cloud.elastic.co',
         '--xpack.cloud.deployment_url=/deployments/deploymentId',
         '--xpack.cloud.organization_url=/organization/organizationId',
diff --git a/x-pack/test/functional_cloud/tests/cloud_links.ts b/x-pack/test/functional_cloud/tests/cloud_links.ts
index 873cd943ec59d..94f67401c98fd 100644
--- a/x-pack/test/functional_cloud/tests/cloud_links.ts
+++ b/x-pack/test/functional_cloud/tests/cloud_links.ts
@@ -46,6 +46,28 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
           await find.byCssSelector('[data-test-subj="cloudOnboardingSetupGuideLink"]')
         ).to.not.be(null);
       });
+
+      it('A button to open a modal to view the CloudID and ES endpoint is added', async () => {
+        await PageObjects.common.clickAndValidate('helpMenuButton', 'endpointsHelpLink');
+        expect(await find.byCssSelector('[data-test-subj="endpointsHelpLink"]')).to.not.be(null);
+
+        // Open the modal
+        await PageObjects.common.clickAndValidate('endpointsHelpLink', 'deploymentDetailsModal');
+
+        const esEndpointInput = await find.byCssSelector(
+          '[data-test-subj="deploymentDetailsEsEndpoint"]'
+        );
+        const esEndpointValue = await esEndpointInput.getAttribute('value');
+        expect(esEndpointValue).to.be('https://ES123abc.hello.com:443');
+
+        const cloudIdInput = await find.byCssSelector(
+          '[data-test-subj="deploymentDetailsCloudID"]'
+        );
+        const cloudIdInputValue = await cloudIdInput.getAttribute('value');
+        expect(cloudIdInputValue).to.be(
+          'ftr_fake_cloud_id:aGVsbG8uY29tOjQ0MyRFUzEyM2FiYyRrYm4xMjNhYmM='
+        );
+      });
     });
 
     it('"Manage this deployment" is appended to the nav list', async () => {
diff --git a/yarn.lock b/yarn.lock
index 41769ceb9a6ef..9c52f8dd23ae4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3243,6 +3243,10 @@
   version "0.0.0"
   uid ""
 
+"@kbn/cloud@link:packages/cloud":
+  version "0.0.0"
+  uid ""
+
 "@kbn/code-editor-mocks@link:packages/shared-ux/code_editor/mocks":
   version "0.0.0"
   uid ""

From 261febcf310552c2280a369f6f740c85bae7d85a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cau=C3=AA=20Marcondes?=
 <55978943+cauemarcondes@users.noreply.github.com>
Date: Thu, 28 Sep 2023 12:43:02 +0100
Subject: [PATCH 10/20] [Profiling] fixing TopN functions sorting (#167242)

![sorting](https://github.com/elastic/kibana/assets/55978943/f5ad2b2c-0dff-4517-ac0f-88c9d99d6b2e)
---
 .../e2e/profiling_views/functions.cy.ts       |  78 ++++++++++++
 .../components/topn_functions/index.tsx       |  73 +++++++-----
 .../public/components/topn_functions/utils.ts | 111 +++++++++---------
 x-pack/plugins/profiling/scripts/test/e2e.js  |   6 +-
 4 files changed, 178 insertions(+), 90 deletions(-)

diff --git a/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts b/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts
index 7ad922ceb7936..f928db33e2f19 100644
--- a/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts
+++ b/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts
@@ -87,4 +87,82 @@ describe('Functions page', () => {
     const firstRowSelector = '[data-grid-row-index="0"] [data-test-subj="dataGridRowCell"]';
     cy.get(firstRowSelector).eq(2).contains('libjvm.so');
   });
+
+  it('Sorting grid', () => {
+    cy.intercept('GET', '/internal/profiling/topn/functions?*').as('getTopNFunctions');
+    cy.visitKibana('/app/profiling/functions', { rangeFrom, rangeTo });
+    cy.wait('@getTopNFunctions');
+    [
+      {
+        columnKey: 'rank',
+        columnIndex: 1,
+        highRank: 388,
+        lowRank: 1,
+        highValue: 388,
+        lowValue: 1,
+      },
+      {
+        columnKey: 'samples',
+        columnIndex: 7,
+        highRank: 1,
+        lowRank: 44,
+        highValue: 28,
+        lowValue: 1,
+      },
+      {
+        columnKey: 'selfCPU',
+        columnIndex: 3,
+        highRank: 1,
+        lowRank: 44,
+        highValue: '5.46%',
+        lowValue: '0.19%',
+      },
+      {
+        columnKey: 'totalCPU',
+        columnIndex: 4,
+        highRank: 338,
+        lowRank: 44,
+        highValue: '10.33%',
+        lowValue: '0.19%',
+      },
+      {
+        columnKey: 'annualizedCo2',
+        columnIndex: 5,
+        highRank: 1,
+        lowRank: 44,
+        highValue: '1.84 lbs / 0.84 kg',
+        lowValue: '0.07 lbs / 0.03 kg',
+      },
+      {
+        columnKey: 'annualizedDollarCost',
+        columnIndex: 6,
+        highRank: 1,
+        lowRank: 44,
+        highValue: '$17.37',
+        lowValue: '$0.62',
+      },
+    ].forEach(({ columnKey, columnIndex, highRank, highValue, lowRank, lowValue }) => {
+      cy.get(`[data-test-subj="dataGridHeaderCell-${columnKey}"]`).click();
+      cy.contains('Sort High-Low').click();
+      const firstRowSelector = '[data-grid-row-index="0"] [data-test-subj="dataGridRowCell"]';
+      cy.get(firstRowSelector).eq(1).contains(highRank);
+      cy.get(firstRowSelector).eq(columnIndex).contains(highValue);
+
+      cy.get(`[data-test-subj="dataGridHeaderCell-${columnKey}"]`).click();
+      cy.contains('Sort Low-High').click();
+      cy.get(firstRowSelector).eq(1).contains(lowRank);
+      cy.get(firstRowSelector).eq(columnIndex).contains(lowValue);
+    });
+
+    cy.get(`[data-test-subj="dataGridHeaderCell-frame"]`).click();
+    cy.contains('Sort Z-A').click();
+    const firstRowSelector = '[data-grid-row-index="0"] [data-test-subj="dataGridRowCell"]';
+    cy.get(firstRowSelector).eq(1).contains('1');
+    cy.get(firstRowSelector).eq(2).contains('vmlinux');
+
+    cy.get('[data-test-subj="dataGridHeaderCell-frame"]').click();
+    cy.contains('Sort A-Z').click();
+    cy.get(firstRowSelector).eq(1).contains('371');
+    cy.get(firstRowSelector).eq(2).contains('/');
+  });
 });
diff --git a/x-pack/plugins/profiling/public/components/topn_functions/index.tsx b/x-pack/plugins/profiling/public/components/topn_functions/index.tsx
index 7dbc6c38fb0eb..e1318471913d9 100644
--- a/x-pack/plugins/profiling/public/components/topn_functions/index.tsx
+++ b/x-pack/plugins/profiling/public/components/topn_functions/index.tsx
@@ -16,11 +16,11 @@ import {
   EuiScreenReaderOnly,
 } from '@elastic/eui';
 import { i18n } from '@kbn/i18n';
-import { last } from 'lodash';
+import { useUiTracker } from '@kbn/observability-shared-plugin/public';
+import { getCalleeFunction, TopNFunctions, TopNFunctionSortField } from '@kbn/profiling-utils';
+import { last, orderBy } from 'lodash';
 import React, { forwardRef, Ref, useMemo, useState } from 'react';
 import { GridOnScrollProps } from 'react-window';
-import { useUiTracker } from '@kbn/observability-shared-plugin/public';
-import { TopNFunctions, TopNFunctionSortField } from '@kbn/profiling-utils';
 import { CPULabelWithHint } from '../cpu_label_with_hint';
 import { FrameInformationTooltip } from '../frame_information_window/frame_information_tooltip';
 import { LabelWithHint } from '../label_with_hint';
@@ -102,10 +102,33 @@ export const TopNFunctionsGrid = forwardRef(
       totalSeconds,
     ]);
 
+    const sortedRows = useMemo(() => {
+      switch (sortField) {
+        case TopNFunctionSortField.Frame:
+          return orderBy(rows, (row) => getCalleeFunction(row.frame), sortDirection);
+        case TopNFunctionSortField.SelfCPU:
+          return orderBy(rows, (row) => row.selfCPUPerc, sortDirection);
+        case TopNFunctionSortField.TotalCPU:
+          return orderBy(rows, (row) => row.totalCPUPerc, sortDirection);
+        case TopNFunctionSortField.AnnualizedCo2:
+          return orderBy(rows, (row) => row.impactEstimates?.selfCPU.annualizedCo2, sortDirection);
+        case TopNFunctionSortField.AnnualizedDollarCost:
+          return orderBy(
+            rows,
+            (row) => row.impactEstimates?.selfCPU.annualizedDollarCost,
+            sortDirection
+          );
+        default:
+          return orderBy(rows, sortField, sortDirection);
+      }
+    }, [rows, sortDirection, sortField]);
+
     const { columns, leadingControlColumns } = useMemo(() => {
       const gridColumns: EuiDataGridColumn[] = [
         {
           id: TopNFunctionSortField.Rank,
+          schema: 'numeric',
+          actions: { showHide: false },
           initialWidth: isDifferentialView ? 50 : 90,
           displayAsText: i18n.translate('xpack.profiling.functionsView.rankColumnLabel', {
             defaultMessage: 'Rank',
@@ -113,6 +136,7 @@ export const TopNFunctionsGrid = forwardRef(
         },
         {
           id: TopNFunctionSortField.Frame,
+          actions: { showHide: false },
           displayAsText: i18n.translate('xpack.profiling.functionsView.functionColumnLabel', {
             defaultMessage: 'Function',
           }),
@@ -120,7 +144,8 @@ export const TopNFunctionsGrid = forwardRef(
         {
           id: TopNFunctionSortField.Samples,
           initialWidth: isDifferentialView ? 100 : 200,
-          schema: 'samples',
+          schema: 'numeric',
+          actions: { showHide: false },
           display: (
             <LabelWithHint
               label={i18n.translate('xpack.profiling.functionsView.samplesColumnLabel', {
@@ -137,6 +162,8 @@ export const TopNFunctionsGrid = forwardRef(
         },
         {
           id: TopNFunctionSortField.SelfCPU,
+          schema: 'numeric',
+          actions: { showHide: false },
           initialWidth: isDifferentialView ? 100 : 200,
           display: (
             <CPULabelWithHint
@@ -149,6 +176,8 @@ export const TopNFunctionsGrid = forwardRef(
         },
         {
           id: TopNFunctionSortField.TotalCPU,
+          schema: 'numeric',
+          actions: { showHide: false },
           initialWidth: isDifferentialView ? 100 : 200,
           display: (
             <CPULabelWithHint
@@ -166,6 +195,7 @@ export const TopNFunctionsGrid = forwardRef(
         gridColumns.push({
           initialWidth: 60,
           id: TopNFunctionSortField.Diff,
+          actions: { showHide: false },
           displayAsText: i18n.translate('xpack.profiling.functionsView.diffColumnLabel', {
             defaultMessage: 'Diff',
           }),
@@ -176,6 +206,7 @@ export const TopNFunctionsGrid = forwardRef(
         gridColumns.push(
           {
             id: TopNFunctionSortField.AnnualizedCo2,
+            actions: { showHide: false },
             initialWidth: isDifferentialView ? 100 : 200,
             schema: 'numeric',
             display: (
@@ -195,6 +226,8 @@ export const TopNFunctionsGrid = forwardRef(
           },
           {
             id: TopNFunctionSortField.AnnualizedDollarCost,
+            schema: 'numeric',
+            actions: { showHide: false },
             initialWidth: isDifferentialView ? 100 : 200,
             display: (
               <LabelWithHint
@@ -225,7 +258,7 @@ export const TopNFunctionsGrid = forwardRef(
           rowCellRender: function RowCellRender({ rowIndex }) {
             function handleOnClick() {
               trackProfilingEvent({ metric: 'topN_function_details_click' });
-              setSelectedRow(rows[rowIndex]);
+              setSelectedRow(sortedRows[rowIndex]);
             }
             return (
               <EuiButtonIcon
@@ -240,7 +273,7 @@ export const TopNFunctionsGrid = forwardRef(
         });
       }
       return { columns: gridColumns, leadingControlColumns: gridLeadingControlColumns };
-    }, [isDifferentialView, rows, showDiffColumn, trackProfilingEvent]);
+    }, [isDifferentialView, sortedRows, showDiffColumn, trackProfilingEvent]);
 
     const [visibleColumns, setVisibleColumns] = useState(columns.map(({ id }) => id));
 
@@ -249,7 +282,7 @@ export const TopNFunctionsGrid = forwardRef(
       columnId,
       setCellProps,
     }: EuiDataGridCellValueElementProps) {
-      const data = rows[rowIndex];
+      const data = sortedRows[rowIndex];
       if (data) {
         return (
           <FunctionRow
@@ -272,9 +305,8 @@ export const TopNFunctionsGrid = forwardRef(
           aria-label="TopN functions"
           columns={columns}
           columnVisibility={{ visibleColumns, setVisibleColumns }}
-          rowCount={rows.length}
+          rowCount={sortedRows.length > 100 ? 100 : sortedRows.length}
           renderCellValue={RenderCellValue}
-          inMemory={{ level: 'sorting' }}
           sorting={{ columns: [{ id: sortField, direction: sortDirection }], onSort }}
           leadingControlColumns={leadingControlColumns}
           pagination={{
@@ -296,29 +328,6 @@ export const TopNFunctionsGrid = forwardRef(
           virtualizationOptions={{
             onScroll,
           }}
-          schemaDetectors={[
-            {
-              type: 'samples',
-              comparator: (a, b, direction) => {
-                const aNumber = parseFloat(a.replace(/,/g, ''));
-                const bNumber = parseFloat(b.replace(/,/g, ''));
-
-                if (aNumber < bNumber) {
-                  return direction === 'desc' ? 1 : -1;
-                }
-                if (aNumber > bNumber) {
-                  return direction === 'desc' ? -1 : 1;
-                }
-                return 0;
-              },
-              detector: (a) => {
-                return 1;
-              },
-              icon: '',
-              sortTextAsc: 'Low-High',
-              sortTextDesc: 'High-Low',
-            },
-          ]}
         />
         {selectedRow && (
           <FrameInformationTooltip
diff --git a/x-pack/plugins/profiling/public/components/topn_functions/utils.ts b/x-pack/plugins/profiling/public/components/topn_functions/utils.ts
index b6603ab7c203e..5257b41b5ade5 100644
--- a/x-pack/plugins/profiling/public/components/topn_functions/utils.ts
+++ b/x-pack/plugins/profiling/public/components/topn_functions/utils.ts
@@ -70,65 +70,62 @@ export function getFunctionsRows({
     ? keyBy(comparisonTopNFunctions.TopN, 'Id')
     : {};
 
-  return topNFunctions.TopN.filter((topN) => topN.CountExclusive > 0)
-    .slice(0, 100)
-    .map((topN, i) => {
-      const comparisonRow = comparisonDataById?.[topN.Id];
-
-      const scaledSelfCPU = scaleValue({
-        value: topN.CountExclusive,
-        scaleFactor: baselineScaleFactor,
-      });
-
-      const totalCPUPerc = (topN.CountInclusive / topNFunctions.TotalCount) * 100;
-      const selfCPUPerc = (topN.CountExclusive / topNFunctions.TotalCount) * 100;
-
-      const impactEstimates =
-        totalSeconds > 0
-          ? calculateImpactEstimates({
-              countExclusive: topN.CountExclusive,
-              countInclusive: topN.CountInclusive,
-              totalSamples: topNFunctions.TotalCount,
-              totalSeconds,
-            })
-          : undefined;
-
-      function calculateDiff() {
-        if (comparisonTopNFunctions && comparisonRow) {
-          const comparisonScaledSelfCPU = scaleValue({
-            value: comparisonRow.CountExclusive,
-            scaleFactor: comparisonScaleFactor,
-          });
-
-          const scaledDiffSamples = scaledSelfCPU - comparisonScaledSelfCPU;
-
-          return {
-            rank: topN.Rank - comparisonRow.Rank,
-            samples: scaledDiffSamples,
-            selfCPU: comparisonRow.CountExclusive,
-            totalCPU: comparisonRow.CountInclusive,
-            selfCPUPerc:
-              selfCPUPerc -
-              (comparisonRow.CountExclusive / comparisonTopNFunctions.TotalCount) * 100,
-            totalCPUPerc:
-              totalCPUPerc -
-              (comparisonRow.CountInclusive / comparisonTopNFunctions.TotalCount) * 100,
-          };
-        }
-      }
+  return topNFunctions.TopN.filter((topN) => topN.CountExclusive > 0).map((topN, i) => {
+    const comparisonRow = comparisonDataById?.[topN.Id];
 
-      return {
-        rank: topN.Rank,
-        frame: topN.Frame,
-        samples: scaledSelfCPU,
-        selfCPUPerc,
-        totalCPUPerc,
-        selfCPU: topN.CountExclusive,
-        totalCPU: topN.CountInclusive,
-        impactEstimates,
-        diff: calculateDiff(),
-      };
+    const scaledSelfCPU = scaleValue({
+      value: topN.CountExclusive,
+      scaleFactor: baselineScaleFactor,
     });
+
+    const totalCPUPerc = (topN.CountInclusive / topNFunctions.TotalCount) * 100;
+    const selfCPUPerc = (topN.CountExclusive / topNFunctions.TotalCount) * 100;
+
+    const impactEstimates =
+      totalSeconds > 0
+        ? calculateImpactEstimates({
+            countExclusive: topN.CountExclusive,
+            countInclusive: topN.CountInclusive,
+            totalSamples: topNFunctions.TotalCount,
+            totalSeconds,
+          })
+        : undefined;
+
+    function calculateDiff() {
+      if (comparisonTopNFunctions && comparisonRow) {
+        const comparisonScaledSelfCPU = scaleValue({
+          value: comparisonRow.CountExclusive,
+          scaleFactor: comparisonScaleFactor,
+        });
+
+        const scaledDiffSamples = scaledSelfCPU - comparisonScaledSelfCPU;
+
+        return {
+          rank: topN.Rank - comparisonRow.Rank,
+          samples: scaledDiffSamples,
+          selfCPU: comparisonRow.CountExclusive,
+          totalCPU: comparisonRow.CountInclusive,
+          selfCPUPerc:
+            selfCPUPerc - (comparisonRow.CountExclusive / comparisonTopNFunctions.TotalCount) * 100,
+          totalCPUPerc:
+            totalCPUPerc -
+            (comparisonRow.CountInclusive / comparisonTopNFunctions.TotalCount) * 100,
+        };
+      }
+    }
+
+    return {
+      rank: topN.Rank,
+      frame: topN.Frame,
+      samples: scaledSelfCPU,
+      selfCPUPerc,
+      totalCPUPerc,
+      selfCPU: topN.CountExclusive,
+      totalCPU: topN.CountInclusive,
+      impactEstimates,
+      diff: calculateDiff(),
+    };
+  });
 }
 
 export function calculateBaseComparisonDiff({
diff --git a/x-pack/plugins/profiling/scripts/test/e2e.js b/x-pack/plugins/profiling/scripts/test/e2e.js
index d5cd56175d223..8a584a84bdc87 100644
--- a/x-pack/plugins/profiling/scripts/test/e2e.js
+++ b/x-pack/plugins/profiling/scripts/test/e2e.js
@@ -76,7 +76,11 @@ function runTests() {
 
   return childProcess.spawnSync('node', spawnArgs, {
     cwd: e2eDir,
-    env: { ...process.env, CYPRESS_CLI_ARGS: JSON.stringify(cypressCliArgs) },
+    env: {
+      ...process.env,
+      CYPRESS_CLI_ARGS: JSON.stringify(cypressCliArgs),
+      NODE_OPTIONS: '--openssl-legacy-provider',
+    },
     encoding: 'utf8',
     stdio: 'inherit',
   });

From 6ab0c68ae67f18f305a8d2eff4ca7ef5ec6f3529 Mon Sep 17 00:00:00 2001
From: Maryam Saeidi <maryam.saeidi@elastic.co>
Date: Thu, 28 Sep 2023 13:51:20 +0200
Subject: [PATCH 11/20] [AO] Unskip the custom threshold rule executor unit
 test (#167120)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Fixes #162299
Fixes #162540

## Summary

This PR unskips the custom threshold rule executor unit test and removes
the warning implementation from the BE.

## 🧪 How to test
- Create a custom threshold rule, it should work as before. (Warning
implementation logic was already removed from FE; this PR only removes
the BE implementation.)
---
 .../metric_threshold_executor.test.ts         |   1 -
 .../custom_threshold_executor.test.ts         | 274 +++++-------------
 .../custom_threshold_executor.ts              |   6 +-
 .../lib/create_bucket_selector.ts             |  31 +-
 .../custom_threshold/lib/evaluate_rule.ts     |   5 +-
 .../rules/custom_threshold/lib/get_data.ts    |  17 +-
 .../register_custom_threshold_rule_type.ts    |   1 -
 .../lib/rules/custom_threshold/types.ts       |   2 -
 8 files changed, 73 insertions(+), 264 deletions(-)

diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
index 4b618dbd82e85..6eec0c9f28629 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
@@ -131,7 +131,6 @@ const setEvaluationResults = (response: Array<Record<string, Evaluation>>) => {
   jest.requireMock('./lib/evaluate_rule').evaluateRule.mockImplementation(() => response);
 };
 
-// FAILING: https://github.com/elastic/kibana/issues/155534
 describe('The metric threshold alert type', () => {
   describe('querying the entire infrastructure', () => {
     afterAll(() => clearInstances());
diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts
index 4a4640e6e1f2f..d05af5d609b8c 100644
--- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts
+++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts
@@ -11,6 +11,8 @@ import {
   RuleExecutorServicesMock,
   alertsMock,
 } from '@kbn/alerting-plugin/server/mocks';
+import { searchSourceCommonMock } from '@kbn/data-plugin/common/search/search_source/mocks';
+import type { ISearchSource } from '@kbn/data-plugin/common';
 import { LifecycleAlertServices } from '@kbn/rule-registry-plugin/server';
 import { ruleRegistryMocks } from '@kbn/rule-registry-plugin/server/mocks';
 import {
@@ -69,6 +71,14 @@ const mockOptions = {
   executionId: '',
   startedAt: STARTED_AT_MOCK_DATE,
   previousStartedAt: null,
+  params: {
+    searchConfiguration: {
+      query: {
+        query: '',
+        language: 'kuery',
+      },
+    },
+  },
   state: {
     wrapped: initialRuleState,
     trackedAlerts: {
@@ -123,8 +133,7 @@ const setEvaluationResults = (response: Array<Record<string, Evaluation>>) => {
   jest.requireMock('./lib/evaluate_rule').evaluateRule.mockImplementation(() => response);
 };
 
-// FAILING: https://github.com/elastic/kibana/issues/155534
-describe.skip('The metric threshold alert type', () => {
+describe('The metric threshold alert type', () => {
   describe('querying the entire infrastructure', () => {
     afterAll(() => clearInstances());
     const instanceID = '*';
@@ -133,6 +142,7 @@ describe.skip('The metric threshold alert type', () => {
         ...mockOptions,
         services,
         params: {
+          ...mockOptions.params,
           sourceId,
           criteria: [
             {
@@ -147,7 +157,6 @@ describe.skip('The metric threshold alert type', () => {
       comparator: Comparator,
       threshold: number[],
       shouldFire: boolean = false,
-      shouldWarn: boolean = false,
       isNoData: boolean = false
     ) =>
       setEvaluationResults([
@@ -160,7 +169,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire,
-            shouldWarn,
             isNoData,
             bucketKey: { groupBy0: '*' },
           },
@@ -224,7 +232,7 @@ describe.skip('The metric threshold alert type', () => {
       setResults(Comparator.GT, [0.75], true);
       await execute(Comparator.GT, [0.75]);
       const { action } = mostRecentAction(instanceID);
-      expect(action.group).toBe('*');
+      expect(action.group).toBeUndefined();
       expect(action.reason).toContain('is 1');
       expect(action.reason).toContain('Alert when > 0.75');
       expect(action.reason).toContain('test.metric.1');
@@ -237,7 +245,7 @@ describe.skip('The metric threshold alert type', () => {
     const execute = (
       comparator: Comparator,
       threshold: number[],
-      groupBy: string[] = ['something'],
+      groupBy: string[] = ['groupByField'],
       metric?: string,
       state?: any
     ) =>
@@ -245,6 +253,7 @@ describe.skip('The metric threshold alert type', () => {
         ...mockOptions,
         services,
         params: {
+          ...mockOptions.params,
           groupBy,
           criteria: [
             {
@@ -270,7 +279,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'a' },
           },
@@ -282,7 +290,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'b' },
           },
@@ -303,7 +310,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'a' },
           },
@@ -315,7 +321,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3,
             timestamp: new Date().toISOString(),
             shouldFire: false,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'b' },
           },
@@ -336,7 +341,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: false,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'a' },
           },
@@ -348,7 +352,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3,
             timestamp: new Date().toISOString(),
             shouldFire: false,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'b' },
           },
@@ -369,7 +372,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'a' },
           },
@@ -381,15 +383,14 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'b' },
           },
         },
       ]);
       await execute(Comparator.GT, [0.75]);
-      expect(mostRecentAction(instanceIdA).action.group).toBe('a');
-      expect(mostRecentAction(instanceIdB).action.group).toBe('b');
+      expect(mostRecentAction(instanceIdA).action.group).toEqual({ groupByField: 'a' });
+      expect(mostRecentAction(instanceIdB).action.group).toEqual({ groupByField: 'b' });
     });
     test('persists previous groups that go missing, until the groupBy param changes', async () => {
       setEvaluationResults([
@@ -402,7 +403,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'a' },
           },
@@ -414,7 +414,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'b' },
           },
@@ -426,7 +425,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'c' },
           },
@@ -435,7 +433,7 @@ describe.skip('The metric threshold alert type', () => {
       const { state: stateResult1 } = await execute(
         Comparator.GT,
         [0.75],
-        ['something'],
+        ['groupByField'],
         'test.metric.2'
       );
       expect(stateResult1.missingGroups).toEqual(expect.arrayContaining([]));
@@ -449,7 +447,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'a' },
           },
@@ -461,7 +458,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'b' },
           },
@@ -473,7 +469,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: null,
             timestamp: new Date().toISOString(),
             shouldFire: false,
-            shouldWarn: false,
             isNoData: true,
             bucketKey: { groupBy0: 'c' },
           },
@@ -482,7 +477,7 @@ describe.skip('The metric threshold alert type', () => {
       const { state: stateResult2 } = await execute(
         Comparator.GT,
         [0.75],
-        ['something'],
+        ['groupByField'],
         'test.metric.1',
         stateResult1
       );
@@ -499,7 +494,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'a' },
           },
@@ -511,7 +505,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'b' },
           },
@@ -520,7 +513,7 @@ describe.skip('The metric threshold alert type', () => {
       const { state: stateResult3 } = await execute(
         Comparator.GT,
         [0.75],
-        ['something', 'something-else'],
+        ['groupByField', 'groupByField-else'],
         'test.metric.1',
         stateResult2
       );
@@ -538,7 +531,8 @@ describe.skip('The metric threshold alert type', () => {
         ...mockOptions,
         services,
         params: {
-          groupBy: ['something'],
+          ...mockOptions.params,
+          groupBy: ['groupByField'],
           criteria: [
             {
               ...baseNonCountCriterion,
@@ -547,7 +541,12 @@ describe.skip('The metric threshold alert type', () => {
               metric: metric ?? baseNonCountCriterion.metric,
             },
           ],
-          filterQuery,
+          searchConfiguration: {
+            query: {
+              query: filterQuery,
+              language: 'kuery',
+            },
+          },
         },
         state: state ?? mockOptions.state.wrapped,
       });
@@ -562,7 +561,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'a' },
           },
@@ -574,7 +572,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'b' },
           },
@@ -586,7 +583,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'c' },
           },
@@ -609,7 +605,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'a' },
           },
@@ -621,7 +616,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'b' },
           },
@@ -633,7 +627,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: null,
             timestamp: new Date().toISOString(),
             shouldFire: false,
-            shouldWarn: false,
             isNoData: true,
             bucketKey: { groupBy0: 'c' },
           },
@@ -659,7 +652,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'a' },
           },
@@ -671,7 +663,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'b' },
           },
@@ -701,6 +692,7 @@ describe.skip('The metric threshold alert type', () => {
         ...mockOptions,
         services,
         params: {
+          ...mockOptions.params,
           groupBy,
           criteria: [
             {
@@ -731,7 +723,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'host-01' },
             context: {
@@ -746,7 +737,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'host-02' },
             context: {
@@ -784,6 +774,7 @@ describe.skip('The metric threshold alert type', () => {
         ...mockOptions,
         services,
         params: {
+          ...mockOptions.params,
           groupBy,
           criteria: [
             {
@@ -812,7 +803,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: '*' },
           },
@@ -838,6 +828,7 @@ describe.skip('The metric threshold alert type', () => {
         ...mockOptions,
         services,
         params: {
+          ...mockOptions.params,
           sourceId,
           groupBy,
           criteria: [
@@ -866,7 +857,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: '*' },
           },
@@ -880,7 +870,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: '*' },
           },
@@ -901,7 +890,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: '*' },
           },
@@ -923,7 +911,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'a' },
           },
@@ -935,7 +922,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'b' },
           },
@@ -949,7 +935,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'a' },
           },
@@ -961,7 +946,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: false,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'b' },
           },
@@ -969,7 +953,7 @@ describe.skip('The metric threshold alert type', () => {
       ]);
       const instanceIdA = 'a';
       const instanceIdB = 'b';
-      await execute(Comparator.GT_OR_EQ, [1.0], [3.0], 'something');
+      await execute(Comparator.GT_OR_EQ, [1.0], [3.0], 'groupByField');
       expect(mostRecentAction(instanceIdA)).toBeAlertAction();
       expect(mostRecentAction(instanceIdB)).toBe(undefined);
     });
@@ -984,7 +968,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: '*' },
           },
@@ -998,7 +981,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: '*' },
           },
@@ -1027,6 +1009,7 @@ describe.skip('The metric threshold alert type', () => {
         ...mockOptions,
         services,
         params: {
+          ...mockOptions.params,
           sourceId,
           criteria: [
             {
@@ -1048,7 +1031,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'a' },
           },
@@ -1066,7 +1048,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1,
             timestamp: new Date().toISOString(),
             shouldFire: false,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'a' },
           },
@@ -1086,8 +1067,9 @@ describe.skip('The metric threshold alert type', () => {
           ...mockOptions,
           services,
           params: {
+            ...mockOptions.params,
             sourceId,
-            groupBy: 'something',
+            groupBy: 'groupByField',
             criteria: [
               {
                 ...baseCountCriterion,
@@ -1112,7 +1094,6 @@ describe.skip('The metric threshold alert type', () => {
               currentValue: 1,
               timestamp: new Date().toISOString(),
               shouldFire: false,
-              shouldWarn: false,
               isNoData: false,
               bucketKey: { groupBy0: 'a' },
             },
@@ -1124,7 +1105,6 @@ describe.skip('The metric threshold alert type', () => {
               currentValue: 1,
               timestamp: new Date().toISOString(),
               shouldFire: false,
-              shouldWarn: false,
               isNoData: false,
               bucketKey: { groupBy0: 'b' },
             },
@@ -1143,7 +1123,6 @@ describe.skip('The metric threshold alert type', () => {
               currentValue: 0,
               timestamp: new Date().toISOString(),
               shouldFire: true,
-              shouldWarn: false,
               isNoData: false,
               bucketKey: { groupBy0: 'a' },
             },
@@ -1155,7 +1134,6 @@ describe.skip('The metric threshold alert type', () => {
               currentValue: 0,
               timestamp: new Date().toISOString(),
               shouldFire: true,
-              shouldWarn: false,
               isNoData: false,
               bucketKey: { groupBy0: 'b' },
             },
@@ -1175,6 +1153,7 @@ describe.skip('The metric threshold alert type', () => {
         ...mockOptions,
         services,
         params: {
+          ...mockOptions.params,
           criteria: [
             {
               ...baseNonCountCriterion,
@@ -1197,7 +1176,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: '*' },
           },
@@ -1215,7 +1193,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3,
             timestamp: new Date().toISOString(),
             shouldFire: false,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: '*' },
           },
@@ -1233,6 +1210,7 @@ describe.skip('The metric threshold alert type', () => {
         ...mockOptions,
         services,
         params: {
+          ...mockOptions.params,
           sourceId,
           criteria: [
             {
@@ -1256,7 +1234,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: '*' },
           },
@@ -1274,7 +1251,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: false,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: '*' },
           },
@@ -1292,6 +1268,7 @@ describe.skip('The metric threshold alert type', () => {
         ...mockOptions,
         services,
         params: {
+          ...mockOptions.params,
           sourceId,
           criteria: [
             {
@@ -1315,7 +1292,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: null,
             timestamp: new Date().toISOString(),
             shouldFire: false,
-            shouldWarn: false,
             isNoData: true,
             bucketKey: { groupBy0: '*' },
           },
@@ -1337,7 +1313,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: null,
             timestamp: new Date().toISOString(),
             shouldFire: false,
-            shouldWarn: false,
             isNoData: true,
             bucketKey: { groupBy0: '*' },
           },
@@ -1356,6 +1331,7 @@ describe.skip('The metric threshold alert type', () => {
         ...mockOptions,
         services,
         params: {
+          ...mockOptions.params,
           sourceId,
           criteria: [
             {
@@ -1384,7 +1360,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: null,
             timestamp: STARTED_AT_MOCK_DATE.toISOString(),
             shouldFire: false,
-            shouldWarn: false,
             isNoData: true,
             bucketKey: { groupBy0: '*' },
           },
@@ -1395,15 +1370,9 @@ describe.skip('The metric threshold alert type', () => {
       const recentAction = mostRecentAction(instanceID);
       expect(recentAction.action).toEqual({
         alertDetailsUrl: '',
-        alertState: 'NO DATA',
-        group: '*',
-        groupByKeys: undefined,
-        metric: { condition0: 'test.metric.3', condition1: 'count' },
         reason: 'test.metric.3 reported no data in the last 1m',
-        threshold: { condition0: ['1'], condition1: [30] },
         timestamp: STARTED_AT_MOCK_DATE.toISOString(),
-        value: { condition0: '[NO DATA]', condition1: 0 },
-        viewInAppUrl: 'http://localhost:5601/app/metrics/explorer',
+        value: ['[NO DATA]', 0],
         tags: [],
       });
       expect(recentAction).toBeNoDataAction();
@@ -1421,7 +1390,8 @@ describe.skip('The metric threshold alert type', () => {
         ...mockOptions,
         services,
         params: {
-          groupBy: 'something',
+          ...mockOptions.params,
+          groupBy: 'groupByField',
           sourceId: 'default',
           criteria: [
             {
@@ -1457,7 +1427,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: null,
             timestamp: new Date().toISOString(),
             shouldFire: false,
-            shouldWarn: false,
             isNoData: true,
             bucketKey: { groupBy0: '*' },
           },
@@ -1475,7 +1444,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: null,
             timestamp: new Date().toISOString(),
             shouldFire: false,
-            shouldWarn: false,
             isNoData: true,
             bucketKey: { groupBy0: '*' },
           },
@@ -1493,7 +1461,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1.0,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'a' },
           },
@@ -1505,7 +1472,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'b' },
           },
@@ -1531,7 +1497,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: null,
             timestamp: new Date().toISOString(),
             shouldFire: false,
-            shouldWarn: false,
             isNoData: true,
             bucketKey: { groupBy0: 'a' },
           },
@@ -1543,7 +1508,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: null,
             timestamp: new Date().toISOString(),
             shouldFire: false,
-            shouldWarn: false,
             isNoData: true,
             bucketKey: { groupBy0: 'b' },
           },
@@ -1565,7 +1529,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'a' },
           },
@@ -1577,7 +1540,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'b' },
           },
@@ -1589,7 +1551,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'c' },
           },
@@ -1610,7 +1571,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 1,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'a' },
           },
@@ -1622,7 +1582,6 @@ describe.skip('The metric threshold alert type', () => {
             currentValue: 3,
             timestamp: new Date().toISOString(),
             shouldFire: true,
-            shouldWarn: false,
             isNoData: false,
             bucketKey: { groupBy0: 'b' },
           },
@@ -1641,7 +1600,8 @@ describe.skip('The metric threshold alert type', () => {
           ...mockOptions,
           services,
           params: {
-            groupBy: 'something',
+            ...mockOptions.params,
+            groupBy: 'groupByField',
             sourceId: 'default',
             criteria: [
               {
@@ -1673,7 +1633,6 @@ describe.skip('The metric threshold alert type', () => {
               currentValue: null,
               timestamp: new Date().toISOString(),
               shouldFire: false,
-              shouldWarn: false,
               isNoData: true,
               bucketKey: { groupBy0: '*' },
             },
@@ -1691,7 +1650,6 @@ describe.skip('The metric threshold alert type', () => {
               currentValue: null,
               timestamp: new Date().toISOString(),
               shouldFire: false,
-              shouldWarn: false,
               isNoData: true,
               bucketKey: { groupBy0: '*' },
             },
@@ -1709,7 +1667,6 @@ describe.skip('The metric threshold alert type', () => {
               currentValue: 1,
               timestamp: new Date().toISOString(),
               shouldFire: true,
-              shouldWarn: false,
               isNoData: false,
               bucketKey: { groupBy0: 'a' },
             },
@@ -1721,7 +1678,6 @@ describe.skip('The metric threshold alert type', () => {
               currentValue: 3,
               timestamp: new Date().toISOString(),
               shouldFire: true,
-              shouldWarn: false,
               isNoData: false,
               bucketKey: { groupBy0: 'b' },
             },
@@ -1745,7 +1701,6 @@ describe.skip('The metric threshold alert type', () => {
               currentValue: null,
               timestamp: new Date().toISOString(),
               shouldFire: false,
-              shouldWarn: false,
               isNoData: true,
               bucketKey: { groupBy0: 'a' },
             },
@@ -1757,7 +1712,6 @@ describe.skip('The metric threshold alert type', () => {
               currentValue: null,
               timestamp: new Date().toISOString(),
               shouldFire: false,
-              shouldWarn: false,
               isNoData: true,
               bucketKey: { groupBy0: 'b' },
             },
@@ -1770,112 +1724,6 @@ describe.skip('The metric threshold alert type', () => {
       });
     });
   });
-
-  describe('attempting to use a malformed filterQuery', () => {
-    afterAll(() => clearInstances());
-    const instanceID = '*';
-    const execute = () =>
-      executor({
-        ...mockOptions,
-        services,
-        params: {
-          criteria: [
-            {
-              ...baseNonCountCriterion,
-            },
-          ],
-          sourceId: 'default',
-          filterQuery:
-            'host.name:(look.there.is.no.space.after.these.parentheses)and uh.oh: "wow that is bad"',
-        },
-      });
-    test('reports an error', async () => {
-      await execute();
-      expect(mostRecentAction(instanceID)).toBeErrorAction();
-    });
-  });
-
-  describe('querying the entire infrastructure with warning threshold', () => {
-    afterAll(() => clearInstances());
-    const instanceID = '*';
-
-    const execute = () =>
-      executor({
-        ...mockOptions,
-        services,
-        params: {
-          sourceId: 'default',
-          criteria: [
-            {
-              ...baseNonCountCriterion,
-              comparator: Comparator.GT,
-              threshold: [9999],
-            },
-          ],
-        },
-      });
-
-    const setResults = ({
-      comparator = Comparator.GT,
-      threshold = [9999],
-      warningComparator = Comparator.GT,
-      warningThreshold = [2.49],
-      metric = 'test.metric.1',
-      currentValue = 7.59,
-      shouldWarn = false,
-    }) =>
-      setEvaluationResults([
-        {
-          '*': {
-            ...baseNonCountCriterion,
-            comparator,
-            threshold,
-            warningComparator,
-            warningThreshold,
-            metric,
-            currentValue,
-            timestamp: new Date().toISOString(),
-            shouldFire: false,
-            shouldWarn,
-            isNoData: false,
-            bucketKey: { groupBy0: '*' },
-          },
-        },
-      ]);
-
-    test('warns as expected with the > comparator', async () => {
-      setResults({ warningThreshold: [2.49], currentValue: 2.5, shouldWarn: true });
-      await execute();
-      expect(mostRecentAction(instanceID)).toBeWarnAction();
-
-      setResults({ warningThreshold: [2.49], currentValue: 1.23, shouldWarn: false });
-      await execute();
-      expect(mostRecentAction(instanceID)).toBe(undefined);
-    });
-
-    test('reports expected warning values to the action context', async () => {
-      setResults({ warningThreshold: [2.49], currentValue: 2.5, shouldWarn: true });
-      await execute();
-
-      const { action } = mostRecentAction(instanceID);
-      expect(action.group).toBe('*');
-      expect(action.reason).toBe('test.metric.1 is 2.5 in the last 1 min. Alert when > 2.49.');
-    });
-
-    test('reports expected warning values to the action context for percentage metric', async () => {
-      setResults({
-        warningThreshold: [0.81],
-        currentValue: 0.82,
-        shouldWarn: true,
-        metric: 'system.cpu.user.pct',
-      });
-      await execute();
-
-      const { action } = mostRecentAction(instanceID);
-      expect(action.group).toBe('*');
-      expect(action.reason).toBe('system.cpu.user.pct is 82% in the last 1 min. Alert when > 81%.');
-    });
-  });
 });
 
 const mockLibs: any = {
@@ -1897,10 +1745,28 @@ const mockLibs: any = {
 const executor = createMetricThresholdExecutor(mockLibs);
 
 const alertsServices = alertsMock.createRuleExecutorServices();
+const mockedIndex = {
+  id: 'c34a7c79-a88b-4b4a-ad19-72f6d24104e4',
+  title: 'metrics-fake_hosts',
+  fieldFormatMap: {},
+  typeMeta: {},
+  timeFieldName: '@timestamp',
+};
+const mockedDataView = {
+  getIndexPattern: () => 'mockedIndexPattern',
+  ...mockedIndex,
+};
+const mockedSearchSource = {
+  getField: jest.fn(() => mockedDataView),
+} as any as ISearchSource;
 const services: RuleExecutorServicesMock &
   LifecycleAlertServices<AlertState, MetricThresholdAlertContext, string> = {
   ...alertsServices,
   ...ruleRegistryMocks.createLifecycleAlertServices(alertsServices),
+  searchSourceClient: {
+    ...searchSourceCommonMock,
+    create: jest.fn(() => Promise.resolve(mockedSearchSource)),
+  },
 };
 services.savedObjectsClient.get.mockImplementation(async (type: string, sourceId: string) => {
   if (sourceId === 'alternate')
@@ -1957,12 +1823,12 @@ function clearInstances() {
 
 interface Action {
   id: string;
-  action: { alertState: string };
+  action: { reason: string };
 }
 
 expect.extend({
   toBeAlertAction(action?: Action) {
-    const pass = action?.id === FIRED_ACTIONS.id && action?.action.alertState === 'ALERT';
+    const pass = action?.id === FIRED_ACTIONS.id && !action?.action.reason.includes('no data');
     const message = () => `expected ${action} to be an ALERT action`;
     return {
       message,
@@ -1970,21 +1836,13 @@ expect.extend({
     };
   },
   toBeNoDataAction(action?: Action) {
-    const pass = action?.id === NO_DATA_ACTIONS.id && action?.action.alertState === 'NO DATA';
+    const pass = action?.id === NO_DATA_ACTIONS.id && action?.action.reason.includes('no data');
     const message = () => `expected ${action} to be a NO DATA action`;
     return {
       message,
       pass,
     };
   },
-  toBeErrorAction(action?: Action) {
-    const pass = action?.id === FIRED_ACTIONS.id && action?.action.alertState === 'ERROR';
-    const message = () => `expected ${action} to be an ERROR action`;
-    return {
-      message,
-      pass,
-    };
-  },
 });
 
 declare global {
@@ -1992,9 +1850,7 @@ declare global {
   namespace jest {
     interface Matchers<R> {
       toBeAlertAction(action?: Action): R;
-      toBeWarnAction(action?: Action): R;
       toBeNoDataAction(action?: Action): R;
-      toBeErrorAction(action?: Action): R;
     }
   }
 }
diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts
index bff471aaea2ec..6f0f146f2a267 100644
--- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts
+++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts
@@ -56,7 +56,7 @@ export type MetricThresholdAlertState = AlertState; // no specific instance stat
 
 export interface MetricThresholdAlertContext extends Record<string, unknown> {
   alertDetailsUrl: string;
-  groupings?: object;
+  group?: object;
   reason?: string;
   timestamp: string; // ISO string
   value?: Array<number | null> | null;
@@ -306,7 +306,7 @@ export const createMetricThresholdExecutor = ({
             alertsLocator,
             basePath.publicBaseUrl
           ),
-          groupings: groupByKeysObjectMapping[group],
+          group: groupByKeysObjectMapping[group],
           reason,
           timestamp,
           value: alertResults.map((result, index) => {
@@ -347,7 +347,7 @@ export const createMetricThresholdExecutor = ({
           alertsLocator,
           basePath.publicBaseUrl
         ),
-        groupings: groupByKeysObjectForRecovered[recoveredAlertId],
+        group: groupByKeysObjectForRecovered[recoveredAlertId],
         timestamp: startedAt.toISOString(),
         ...additionalContext,
       });
diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_bucket_selector.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_bucket_selector.ts
index 6300bfac703f3..83c84af79eda6 100644
--- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_bucket_selector.ts
+++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_bucket_selector.ts
@@ -7,19 +7,11 @@
 
 import {
   Aggregators,
-  Comparator,
   MetricExpressionParams,
 } from '../../../../../common/custom_threshold_rule/types';
 import { createConditionScript } from './create_condition_script';
 import { createLastPeriod } from './wrap_in_period';
 
-const EMPTY_SHOULD_WARN = {
-  bucket_script: {
-    buckets_path: {},
-    script: '0',
-  },
-};
-
 export const createBucketSelector = (
   condition: MetricExpressionParams,
   alertOnGroupDisappear: boolean = false,
@@ -28,7 +20,6 @@ export const createBucketSelector = (
   lastPeriodEnd?: number
 ) => {
   const hasGroupBy = groupBy != null;
-  const hasWarn = condition.warningThreshold != null && condition.warningComparator != null;
   const isPercentile = [Aggregators.P95, Aggregators.P99].includes(condition.aggType);
   const isCount = condition.aggType === Aggregators.COUNT;
   const isRate = condition.aggType === Aggregators.RATE;
@@ -42,20 +33,6 @@ export const createBucketSelector = (
       }]`
     : "currentPeriod['all']>aggregatedValue";
 
-  const shouldWarn = hasWarn
-    ? {
-        bucket_script: {
-          buckets_path: {
-            value: bucketPath,
-          },
-          script: createConditionScript(
-            condition.warningThreshold as number[],
-            condition.warningComparator as Comparator
-          ),
-        },
-      }
-    : EMPTY_SHOULD_WARN;
-
   const shouldTrigger = {
     bucket_script: {
       buckets_path: {
@@ -66,7 +43,6 @@ export const createBucketSelector = (
   };
 
   const aggs: any = {
-    shouldWarn,
     shouldTrigger,
   };
 
@@ -97,17 +73,16 @@ export const createBucketSelector = (
     const evalutionBucketPath =
       alertOnGroupDisappear && lastPeriodEnd
         ? {
-            shouldWarn: 'shouldWarn',
             shouldTrigger: 'shouldTrigger',
             missingGroup: 'missingGroup',
             newOrRecoveredGroup: 'newOrRecoveredGroup',
           }
-        : { shouldWarn: 'shouldWarn', shouldTrigger: 'shouldTrigger' };
+        : { shouldTrigger: 'shouldTrigger' };
 
     const evaluationScript =
       alertOnGroupDisappear && lastPeriodEnd
-        ? '(params.missingGroup != null && params.missingGroup > 0) || (params.shouldWarn != null && params.shouldWarn > 0) || (params.shouldTrigger != null && params.shouldTrigger > 0) || (params.newOrRecoveredGroup != null && params.newOrRecoveredGroup > 0)'
-        : '(params.shouldWarn != null && params.shouldWarn > 0) || (params.shouldTrigger != null && params.shouldTrigger > 0)';
+        ? '(params.missingGroup != null && params.missingGroup > 0)  || (params.shouldTrigger != null && params.shouldTrigger > 0) || (params.newOrRecoveredGroup != null && params.newOrRecoveredGroup > 0)'
+        : '(params.shouldTrigger != null && params.shouldTrigger > 0)';
 
     aggs.evaluation = {
       bucket_selector: {
diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts
index 66007de19b622..33410cbfb9742 100644
--- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts
+++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts
@@ -28,7 +28,6 @@ export type Evaluation = Omit<MetricExpressionParams, 'metric'> & {
   currentValue: number | null;
   timestamp: string;
   shouldFire: boolean;
-  shouldWarn: boolean;
   isNoData: boolean;
   bucketKey: Record<string, string>;
   context?: AdditionalContext;
@@ -91,7 +90,6 @@ export const evaluateRule = async <Params extends EvaluatedRuleParams = Evaluate
           currentValues[missingGroup.key] = {
             value: null,
             trigger: false,
-            warn: false,
             bucketKey: missingGroup.bucketKey,
           };
         }
@@ -100,7 +98,7 @@ export const evaluateRule = async <Params extends EvaluatedRuleParams = Evaluate
       const evaluations: Record<string, Evaluation> = {};
       for (const key of Object.keys(currentValues)) {
         const result = currentValues[key];
-        if (result.trigger || result.warn || result.value === null) {
+        if (result.trigger || result.value === null) {
           evaluations[key] = {
             ...criterion,
             metric:
@@ -114,7 +112,6 @@ export const evaluateRule = async <Params extends EvaluatedRuleParams = Evaluate
             currentValue: result.value,
             timestamp: moment(calculatedTimerange.end).toISOString(),
             shouldFire: result.trigger,
-            shouldWarn: result.warn,
             isNoData: result.value === null,
             bucketKey: result.bucketKey,
             context: {
diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/get_data.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/get_data.ts
index d5a09dbf567a3..37119a22741d8 100644
--- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/get_data.ts
+++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/get_data.ts
@@ -27,7 +27,6 @@ import { getElasticsearchMetricQuery } from './metric_query';
 export type GetDataResponse = Record<
   string,
   {
-    warn: boolean;
     trigger: boolean;
     value: number | null;
     bucketKey: BucketKey;
@@ -49,9 +48,6 @@ interface Aggs {
     };
   };
   aggregatedValue?: AggregatedValue;
-  shouldWarn?: {
-    value: number;
-  };
   shouldTrigger?: {
     value: number;
   };
@@ -98,7 +94,6 @@ const getValue = (aggregatedValue: AggregatedValue, params: MetricExpressionPara
 const NO_DATA_RESPONSE = {
   [UNGROUPED_FACTORY_KEY]: {
     value: null,
-    warn: false,
     trigger: false,
     bucketKey: { groupBy0: UNGROUPED_FACTORY_KEY },
   },
@@ -143,7 +138,6 @@ export const getData = async (
       for (const bucket of groupings.buckets) {
         const key = Object.values(bucket.key).join(',');
         const {
-          shouldWarn,
           shouldTrigger,
           missingGroup,
           currentPeriod,
@@ -163,7 +157,6 @@ export const getData = async (
         if (missingGroup && missingGroup.value > 0) {
           previous[key] = {
             trigger: false,
-            warn: false,
             value: null,
             bucketKey: bucket.key,
           };
@@ -179,7 +172,6 @@ export const getData = async (
 
           previous[key] = {
             trigger: (shouldTrigger && shouldTrigger.value > 0) || false,
-            warn: (shouldWarn && shouldWarn.value > 0) || false,
             value,
             bucketKey: bucket.key,
             container: containerList,
@@ -210,7 +202,6 @@ export const getData = async (
       const {
         currentPeriod,
         aggregatedValue: aggregatedValueForRate,
-        shouldWarn,
         shouldTrigger,
       } = aggs.all.buckets.all;
 
@@ -224,20 +215,15 @@ export const getData = async (
           : aggregatedValue != null
           ? getValue(aggregatedValue, params)
           : null;
-      // There is an edge case where there is no results and the shouldWarn/shouldTrigger
+      // There is an edge case where there is no results and the shouldTrigger
       // bucket scripts will be missing. This is only an issue for document count because
       // the value will end up being ZERO, for other metrics it will be null. In this case
       // we need to do the evaluation in Node.js
       if (aggs.all && params.aggType === Aggregators.COUNT && value === 0) {
         const trigger = comparatorMap[params.comparator](value, params.threshold);
-        const warn =
-          params.warningThreshold && params.warningComparator
-            ? comparatorMap[params.warningComparator](value, params.warningThreshold)
-            : false;
         return {
           [UNGROUPED_FACTORY_KEY]: {
             value,
-            warn,
             trigger,
             bucketKey: { groupBy0: UNGROUPED_FACTORY_KEY },
           },
@@ -246,7 +232,6 @@ export const getData = async (
       return {
         [UNGROUPED_FACTORY_KEY]: {
           value,
-          warn: (shouldWarn && shouldWarn.value > 0) || false,
           trigger: (shouldTrigger && shouldTrigger.value > 0) || false,
           bucketKey: { groupBy0: UNGROUPED_FACTORY_KEY },
         },
diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts
index a3bd05e4316a8..a6466deb25a47 100644
--- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts
+++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts
@@ -69,7 +69,6 @@ export function thresholdRuleType(
     comparator: oneOfLiterals(Object.values(Comparator)),
     timeUnit: schema.string(),
     timeSize: schema.number(),
-    warningComparator: schema.maybe(oneOfLiterals(Object.values(Comparator))),
   };
 
   const nonCountCriterion = schema.object({
diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/types.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/types.ts
index e66042bcb648d..cc63f2cd688d3 100644
--- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/types.ts
+++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/types.ts
@@ -51,8 +51,6 @@ interface BaseMetricExpressionParams {
   timeUnit: TimeUnitChar;
   threshold: number[];
   comparator: Comparator;
-  warningComparator?: Comparator;
-  warningThreshold?: number[];
 }
 
 export interface NonCountMetricExpressionParams extends BaseMetricExpressionParams {

From b12a42261b768ec3b4858d9e2a0f4a053b643ec5 Mon Sep 17 00:00:00 2001
From: Marco Vettorello <marco.vettorello@elastic.co>
Date: Thu, 28 Sep 2023 14:14:58 +0200
Subject: [PATCH 12/20] [Lens] Color mapping for categorical dimensions
 (#162389)

## Summary

This PR introduces the new color mapping feature into Lens.

The color mapping feature is introduced as a standalone sharable
component available from `@kbn/coloring`. The
[README.md](https://github.com/elastic/kibana/blob/ddd216457d66912de43c7688ae99044c2c34bbd2/packages/kbn-coloring/src/shared_components/color_mapping/README.md)
file describes the components and the logic behind it.

The Color Mapping component is also connected to Lens and is available
in the following charts:
- XY (you can specify the mappings from a breakdown dimension
- Partition (you can specify the mappings from the main slice/group by
dimension)
- Tag cloud (you can specify the mappings from the tags dimension)

This MVP feature will be released under the Tech Preview flag.
This PR needs to prove the user experience and the ease of use. UI
styles, design improvements and embellishments will be released in
subsequent PRs.

The current MVP-provided palettes are just a placeholder. I'm
coordinating with @gvnmagni for a final set of palettes.

close https://github.com/elastic/kibana/issues/155037
close https://github.com/elastic/kibana/issues/6480
fix https://github.com/elastic/kibana/issues/28618
fix https://github.com/elastic/kibana/issues/96044
fix https://github.com/elastic/kibana/issues/101942
fix https://github.com/elastic/kibana/issues/112839
fix https://github.com/elastic/kibana/issues/116634


## Release note

This feature introduces the ability to change and map colors to break
down dimensions in Lens. The feature provides an improved way to specify
colors and their association with categories by giving the user a
predefined set of color choices
or customized one that drives the user toward a correct color selection.
It provides ways to pick new colors and generate gradients.
This feature is in Tech Preview and is enabled by default on every new
visualization but can be turned off at will.

![image](https://github.com/elastic/kibana/assets/1421091/d03e59f8-4a6f-4761-ab4c-c53a57c1723a)
---
 .../shared_components/color_mapping/README.md |  87 +++++
 .../__stories__/color_mapping.stories.tsx     | 132 +++++++
 .../categorical_color_mapping.test.tsx        | 115 ++++++
 .../categorical_color_mapping.tsx             |  97 +++++
 .../color/color_handling.test.ts              | 294 +++++++++++++++
 .../color_mapping/color/color_handling.ts     | 164 +++++++++
 .../color_mapping/color/color_math.ts         |  60 ++++
 .../color_mapping/color/rule_matching.ts      |  46 +++
 .../components/assignment/assignment.tsx      | 150 ++++++++
 .../components/assignment/match.tsx           | 101 ++++++
 .../components/assignment/range.tsx           |  67 ++++
 .../assignment/special_assignment.tsx         |  79 ++++
 .../components/color_picker/color_picker.tsx  | 117 ++++++
 .../components/color_picker/color_swatch.tsx  | 184 ++++++++++
 .../color_picker/palette_colors.tsx           | 133 +++++++
 .../components/color_picker/rgb_picker.tsx    | 144 ++++++++
 .../components/container/container.tsx        | 252 +++++++++++++
 .../components/palette_selector/gradient.tsx  | 336 ++++++++++++++++++
 .../palette_selector/palette_selector.tsx     | 263 ++++++++++++++
 .../palette_selector/scale_categorical.tsx    |  17 +
 .../palette_selector/scale_sequential.tsx     |  20 ++
 .../config/assignment_from_categories.ts      |  65 ++++
 .../color_mapping/config/assignments.ts       |  91 +++++
 .../config/default_color_mapping.ts           |  79 ++++
 .../color_mapping/config/index.ts             |   9 +
 .../color_mapping/config/types.ts             | 153 ++++++++
 .../shared_components/color_mapping/index.ts  |  19 +
 .../color_mapping/palettes/elastic_brand.ts   |  28 ++
 .../color_mapping/palettes/eui_amsterdam.ts   |  32 ++
 .../color_mapping/palettes/index.ts           |  37 ++
 .../color_mapping/palettes/kibana_legacy.ts   |  29 ++
 .../color_mapping/palettes/neutral.ts         |  23 ++
 .../color_mapping/state/color_mapping.ts      | 225 ++++++++++++
 .../color_mapping/state/selectors.ts          |  26 ++
 .../color_mapping/state/ui.ts                 |  55 +++
 .../src/shared_components/index.ts            |   2 +
 packages/kbn-coloring/tsconfig.json           |   3 +-
 packages/kbn-optimizer/limits.yml             |   4 +-
 .../common/color_categories.ts                |  41 +++
 src/plugins/chart_expressions/common/index.ts |   1 +
 .../chart_expressions/common/tsconfig.json    |   2 +
 .../expression_legacy_metric/kibana.jsonc     |   3 +-
 .../expression_metric/kibana.jsonc            |   3 +-
 .../mosaic_vis_function.test.ts.snap          |   1 +
 .../pie_vis_function.test.ts.snap             |   2 +
 .../treemap_vis_function.test.ts.snap         |   1 +
 .../waffle_vis_function.test.ts.snap          |   1 +
 .../common/expression_functions/i18n.ts       |   4 +
 .../mosaic_vis_function.ts                    |   5 +
 .../expression_functions/pie_vis_function.ts  |   5 +
 .../treemap_vis_function.ts                   |   5 +
 .../waffle_vis_function.ts                    |   5 +
 .../common/types/expression_renderers.ts      |   1 +
 .../utils/colors/color_mapping_accessors.ts   |  77 ++++
 .../public/utils/layers/get_layers.ts         | 106 ++++--
 .../tagcloud_function.test.ts.snap            |   2 +
 .../expression_functions/tagcloud_function.ts |   8 +
 .../common/types/expression_functions.ts      |   1 +
 .../expression_tagcloud/kibana.jsonc          |   1 +
 .../components/tagcloud_component.test.tsx    |   2 +
 .../public/components/tagcloud_component.tsx  |  65 +++-
 .../tagcloud_renderer.tsx                     |   7 +
 .../expression_tagcloud/tsconfig.json         |   1 +
 .../common_data_layer_args.ts                 |   4 +
 .../common/expression_functions/xy_vis_fn.ts  |   1 +
 .../expression_xy/common/i18n/index.tsx       |   4 +
 .../common/types/expression_functions.ts      |   2 +
 .../__snapshots__/xy_chart.test.tsx.snap      |  10 +
 .../public/components/data_layers.tsx         |   3 +
 .../public/components/xy_chart.tsx            |   1 +
 .../helpers/color/color_mapping_accessor.ts   |  49 +++
 .../public/helpers/color_assignment.ts        |   5 +
 .../public/helpers/data_layers.tsx            |  53 ++-
 src/plugins/charts/kibana.jsonc               |   3 +-
 .../data/common/search/aggs/buckets/index.ts  |   2 +-
 .../search/aggs/buckets/multi_field_key.ts    |  10 +
 .../snapshots/baseline/partial_test_1.json    |   2 +-
 .../snapshots/baseline/tagcloud_all_data.json |   2 +-
 .../baseline/tagcloud_empty_data.json         |   2 +-
 .../snapshots/baseline/tagcloud_fontsize.json |   2 +-
 .../baseline/tagcloud_metric_data.json        |   2 +-
 .../snapshots/baseline/tagcloud_options.json  |   2 +-
 .../snapshots/session/partial_test_1.json     |   2 +-
 .../snapshots/session/tagcloud_all_data.json  |   2 +-
 .../session/tagcloud_empty_data.json          |   2 +-
 .../snapshots/session/tagcloud_fontsize.json  |   2 +-
 .../session/tagcloud_metric_data.json         |   2 +-
 .../snapshots/session/tagcloud_options.json   |   2 +-
 x-pack/plugins/lens/common/types.ts           |   3 +-
 x-pack/plugins/lens/public/app_plugin/app.tsx |   2 +-
 .../editor_frame/state_helpers.ts             |   4 +-
 .../editor_frame/suggestion_helpers.test.ts   |  16 +-
 .../editor_frame/suggestion_helpers.ts        |   8 +-
 .../workspace_panel/chart_switch.test.tsx     |   7 +-
 .../coloring/palette_panel_container.tsx      |  12 +-
 .../shared_components/palette_picker.tsx      |  32 +-
 x-pack/plugins/lens/public/types.ts           |  19 +-
 .../datatable/components/dimension_editor.tsx |   3 +
 .../visualizations/gauge/dimension_editor.tsx |   3 +
 .../visualizations/gauge/visualization.tsx    |   2 +-
 .../heatmap/dimension_editor.tsx              |   3 +
 .../legacy_metric/dimension_editor.tsx        |   3 +
 .../metric/dimension_editor.tsx               |   3 +
 .../visualizations/metric/visualization.tsx   |   2 +-
 .../partition/dimension_editor.tsx            | 171 ++++++++-
 .../partition/suggestions.test.ts             |  13 +-
 .../visualizations/partition/suggestions.ts   |  34 +-
 .../visualizations/partition/to_expression.ts |   4 +-
 .../partition/visualization.tsx               |  57 ++-
 .../public/visualizations/tagcloud/index.ts   |   2 +-
 .../visualizations/tagcloud/suggestions.ts    |   6 +
 .../tagcloud/tagcloud_visualization.tsx       |  42 ++-
 .../tagcloud/tags_dimension_editor.tsx        | 195 +++++++++-
 .../public/visualizations/tagcloud/types.ts   |   3 +-
 .../public/visualizations/xy/to_expression.ts |   3 +-
 .../lens/public/visualizations/xy/types.ts    |   3 +-
 .../visualizations/xy/visualization.test.tsx  |  23 ++
 .../visualizations/xy/visualization.tsx       |  43 ++-
 .../xy/xy_config_panel/dimension_editor.tsx   | 185 +++++++++-
 .../xy_config_panel/xy_config_panel.test.tsx  |   5 +
 .../visualizations/xy/xy_suggestions.test.ts  |  33 +-
 .../visualizations/xy/xy_suggestions.ts       |  20 +-
 x-pack/plugins/lens/tsconfig.json             |   1 +
 .../translations/translations/fr-FR.json      |   1 -
 .../translations/translations/ja-JP.json      |   1 -
 .../translations/translations/zh-CN.json      |   1 -
 .../apps/dashboard/group2/sync_colors.ts      |   2 +
 .../apps/lens/group4/color_mapping.ts         |  73 ++++
 .../functional/apps/lens/group4/colors.ts     |  38 +-
 .../test/functional/apps/lens/group4/index.ts |   1 +
 .../test/functional/page_objects/lens_page.ts |  82 ++++-
 x-pack/test/tsconfig.json                     |   1 +
 132 files changed, 5209 insertions(+), 215 deletions(-)
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/README.md
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/__stories__/color_mapping.stories.tsx
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/categorical_color_mapping.test.tsx
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/categorical_color_mapping.tsx
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/color/color_handling.test.ts
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/color/color_handling.ts
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/color/color_math.ts
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/color/rule_matching.ts
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/assignment.tsx
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/match.tsx
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/range.tsx
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/special_assignment.tsx
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/color_picker.tsx
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/color_swatch.tsx
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/palette_colors.tsx
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/rgb_picker.tsx
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/components/container/container.tsx
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient.tsx
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/palette_selector.tsx
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/scale_categorical.tsx
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/scale_sequential.tsx
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/config/assignment_from_categories.ts
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/config/assignments.ts
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/config/default_color_mapping.ts
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/config/index.ts
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/config/types.ts
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/index.ts
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/palettes/elastic_brand.ts
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/palettes/eui_amsterdam.ts
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/palettes/index.ts
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/palettes/kibana_legacy.ts
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/palettes/neutral.ts
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/state/color_mapping.ts
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/state/selectors.ts
 create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/state/ui.ts
 create mode 100644 src/plugins/chart_expressions/common/color_categories.ts
 create mode 100644 src/plugins/chart_expressions/expression_partition_vis/public/utils/colors/color_mapping_accessors.ts
 create mode 100644 src/plugins/chart_expressions/expression_xy/public/helpers/color/color_mapping_accessor.ts
 create mode 100644 x-pack/test/functional/apps/lens/group4/color_mapping.ts

diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/README.md b/packages/kbn-coloring/src/shared_components/color_mapping/README.md
new file mode 100644
index 0000000000000..220824ca47820
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/README.md
@@ -0,0 +1,87 @@
+# Color Mapping
+
+This shared component can be used to define a color mapping as an association of one or multiple string values to a color definition.
+
+This package provides:
+- a React component, called `CategoricalColorMapping` that provides a simplified UI (that in general can be hosted in a flyout), that helps the user generate a `ColorMapping.Config` object that descibes the mappings configuration
+- a function `getColorFactory` that given a color mapping configuration returns a function that maps a passed category to the corresponding color
+- a definition scheme for the color mapping, based on the type `ColorMapping.Config`, that provides an extensible way of describing the link between colors and rules. Collects the minimal information required apply colors based on categories. Together with the `ColorMappingInputData` can be used to get colors in a deterministic way.
+
+
+An example of the configuration is the following:
+```ts
+const DEFAULT_COLOR_MAPPING_CONFIG: ColorMapping.Config = {
+  assignmentMode: 'auto',
+  assignments: [
+    {
+        rule: {
+            type: 'matchExactly',
+            values: [''];
+        },
+        color: {
+            type: 'categorical',
+            paletteId: 'eui',
+            colorIndex: 2,
+        }
+    }
+  ],
+  specialAssignments: [
+    {
+      rule: {
+        type: 'other',
+      },
+      color: {
+        type: 'categorical',
+        paletteId: 'neutral',
+        colorIndex: 2
+      },
+      touched: false,
+    },
+  ],
+  paletteId: EUIPalette.id,
+  colorMode: {
+    type: 'categorical',
+  },
+};
+```
+
+The function `getColorFactory` is a curry function where, given the model, a palette getter, the theme mode (dark/light) and a list of categories, returns a function that can be used to pick the right color based on a given category.
+
+```ts
+function getColorFactory(
+  model: ColorMapping.Config,
+  getPaletteFn: (paletteId: string) => ColorMapping.CategoricalPalette,
+  isDarkMode: boolean,
+  data: {
+      type: 'categories';
+      categories: Array<string | string[]>;
+    }
+): (category: string | string[]) => Color
+```
+
+
+
+A `category` can be in the shape of a plain string or an array of strings. Numbers, MultiFieldKey, IP etc needs to be stringified.
+
+
+The `CategoricalColorMapping` React component has the following props:
+
+```tsx
+function CategoricalColorMapping(props: {
+  /** The initial color mapping model, usually coming from a the visualization saved object */
+  model: ColorMapping.Config;
+  /** A map of paletteId and palette configuration */
+  palettes: Map<string, ColorMapping.CategoricalPalette>;
+  /** A data description of what needs to be colored */
+  data: ColorMappingInputData;
+  /** Theme dark mode */
+  isDarkMode: boolean;
+  /** A map between original and formatted tokens used to handle special cases, like the Other bucket and the empty bucket */
+  specialTokens: Map<string, string>;
+  /** A function called at every change in the model */
+  onModelUpdate: (model: ColorMapping.Config) => void;
+})
+
+```
+
+the `onModelUpdate` callback is called everytime a change in the model is applied from within the component. Is not called when the `model` prop is updated.
\ No newline at end of file
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/__stories__/color_mapping.stories.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/__stories__/color_mapping.stories.tsx
new file mode 100644
index 0000000000000..95f4ff5623ea3
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/__stories__/color_mapping.stories.tsx
@@ -0,0 +1,132 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { FC } from 'react';
+import { EuiFlyout, EuiForm } from '@elastic/eui';
+import { ComponentStory } from '@storybook/react';
+import { CategoricalColorMapping, ColorMappingProps } from '../categorical_color_mapping';
+import { AVAILABLE_PALETTES } from '../palettes';
+import { DEFAULT_COLOR_MAPPING_CONFIG } from '../config/default_color_mapping';
+
+export default {
+  title: 'Color Mapping',
+  component: CategoricalColorMapping,
+  decorators: [
+    (story: Function) => (
+      <EuiFlyout style={{ width: 350, padding: '8px' }} onClose={() => {}} hideCloseButton>
+        <EuiForm>{story()}</EuiForm>
+      </EuiFlyout>
+    ),
+  ],
+};
+
+const Template: ComponentStory<FC<ColorMappingProps>> = (args) => (
+  <CategoricalColorMapping {...args} />
+);
+
+export const Default = Template.bind({});
+
+Default.args = {
+  model: {
+    ...DEFAULT_COLOR_MAPPING_CONFIG,
+    assignmentMode: 'manual',
+    colorMode: {
+      type: 'gradient',
+      steps: [
+        {
+          type: 'categorical',
+          colorIndex: 0,
+          paletteId: DEFAULT_COLOR_MAPPING_CONFIG.paletteId,
+          touched: false,
+        },
+        {
+          type: 'categorical',
+          colorIndex: 1,
+          paletteId: DEFAULT_COLOR_MAPPING_CONFIG.paletteId,
+          touched: false,
+        },
+        {
+          type: 'categorical',
+          colorIndex: 2,
+          paletteId: DEFAULT_COLOR_MAPPING_CONFIG.paletteId,
+          touched: false,
+        },
+      ],
+      sort: 'asc',
+    },
+    assignments: [
+      {
+        rule: {
+          type: 'matchExactly',
+          values: ['this is', 'a multi-line combobox that is very long and that will be truncated'],
+        },
+        color: {
+          type: 'gradient',
+        },
+        touched: false,
+      },
+      {
+        rule: {
+          type: 'matchExactly',
+          values: ['b', ['double', 'value']],
+        },
+        color: {
+          type: 'gradient',
+        },
+        touched: false,
+      },
+      {
+        rule: {
+          type: 'matchExactly',
+          values: ['c'],
+        },
+        color: {
+          type: 'gradient',
+        },
+        touched: false,
+      },
+      {
+        rule: {
+          type: 'matchExactly',
+          values: [
+            'this is',
+            'a multi-line wrap',
+            'combo box',
+            'test combo',
+            '3 lines',
+            ['double', 'value'],
+          ],
+        },
+        color: {
+          type: 'gradient',
+        },
+        touched: false,
+      },
+    ],
+  },
+  isDarkMode: false,
+  data: {
+    type: 'categories',
+    categories: [
+      'a',
+      'b',
+      'c',
+      'd',
+      'this is',
+      'a multi-line wrap',
+      'combo box',
+      'test combo',
+      '3 lines',
+    ],
+  },
+
+  palettes: AVAILABLE_PALETTES,
+  specialTokens: new Map(),
+  // eslint-disable-next-line no-console
+  onModelUpdate: (model) => console.log(model),
+};
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/categorical_color_mapping.test.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/categorical_color_mapping.test.tsx
new file mode 100644
index 0000000000000..fe8374d7dcdcd
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/categorical_color_mapping.test.tsx
@@ -0,0 +1,115 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+import { mount } from 'enzyme';
+import { CategoricalColorMapping, ColorMappingInputData } from './categorical_color_mapping';
+import { AVAILABLE_PALETTES } from './palettes';
+import { DEFAULT_COLOR_MAPPING_CONFIG } from './config/default_color_mapping';
+import { MULTI_FIELD_KEY_SEPARATOR } from '@kbn/data-plugin/common';
+
+const AUTO_ASSIGN_SWITCH = '[data-test-subj="lns-colorMapping-autoAssignSwitch"]';
+const ASSIGNMENTS_LIST = '[data-test-subj="lns-colorMapping-assignmentsList"]';
+const ASSIGNMENT_ITEM = (i: number) => `[data-test-subj="lns-colorMapping-assignmentsItem${i}"]`;
+
+describe('color mapping', () => {
+  it('load a default color mapping', () => {
+    const dataInput: ColorMappingInputData = {
+      type: 'categories',
+      categories: ['categoryA', 'categoryB'],
+    };
+    const onModelUpdateFn = jest.fn();
+    const component = mount(
+      <CategoricalColorMapping
+        data={dataInput}
+        isDarkMode={false}
+        model={{ ...DEFAULT_COLOR_MAPPING_CONFIG }}
+        palettes={AVAILABLE_PALETTES}
+        onModelUpdate={onModelUpdateFn}
+        specialTokens={new Map()}
+      />
+    );
+
+    expect(component.find(AUTO_ASSIGN_SWITCH).hostNodes().prop('aria-checked')).toEqual(true);
+    expect(component.find(ASSIGNMENTS_LIST).hostNodes().children().length).toEqual(
+      dataInput.categories.length
+    );
+    dataInput.categories.forEach((category, index) => {
+      const assignment = component.find(ASSIGNMENT_ITEM(index)).hostNodes();
+      expect(assignment.text()).toEqual(category);
+      expect(assignment.hasClass('euiComboBox-isDisabled')).toEqual(true);
+    });
+    expect(onModelUpdateFn).not.toBeCalled();
+  });
+
+  it('switch to manual assignments', () => {
+    const dataInput: ColorMappingInputData = {
+      type: 'categories',
+      categories: ['categoryA', 'categoryB'],
+    };
+    const onModelUpdateFn = jest.fn();
+    const component = mount(
+      <CategoricalColorMapping
+        data={dataInput}
+        isDarkMode={false}
+        model={{ ...DEFAULT_COLOR_MAPPING_CONFIG }}
+        palettes={AVAILABLE_PALETTES}
+        onModelUpdate={onModelUpdateFn}
+        specialTokens={new Map()}
+      />
+    );
+    component.find(AUTO_ASSIGN_SWITCH).hostNodes().simulate('click');
+    expect(onModelUpdateFn).toBeCalledTimes(1);
+    expect(component.find(AUTO_ASSIGN_SWITCH).hostNodes().prop('aria-checked')).toEqual(false);
+    expect(component.find(ASSIGNMENTS_LIST).hostNodes().children().length).toEqual(
+      dataInput.categories.length
+    );
+    dataInput.categories.forEach((category, index) => {
+      const assignment = component.find(ASSIGNMENT_ITEM(index)).hostNodes();
+      expect(assignment.text()).toEqual(category);
+      expect(assignment.hasClass('euiComboBox-isDisabled')).toEqual(false);
+    });
+  });
+
+  it('handle special tokens, multi-fields keys and non-trimmed whitespaces', () => {
+    const dataInput: ColorMappingInputData = {
+      type: 'categories',
+      categories: ['__other__', ['fieldA', 'fieldB'], '__empty__', '   with-whitespaces   '],
+    };
+    const onModelUpdateFn = jest.fn();
+    const component = mount(
+      <CategoricalColorMapping
+        data={dataInput}
+        isDarkMode={false}
+        model={{ ...DEFAULT_COLOR_MAPPING_CONFIG }}
+        palettes={AVAILABLE_PALETTES}
+        onModelUpdate={onModelUpdateFn}
+        specialTokens={
+          new Map([
+            ['__other__', 'Other'],
+            ['__empty__', '(Empty)'],
+          ])
+        }
+      />
+    );
+    expect(component.find(ASSIGNMENTS_LIST).hostNodes().children().length).toEqual(
+      dataInput.categories.length
+    );
+    const assignment1 = component.find(ASSIGNMENT_ITEM(0)).hostNodes();
+    expect(assignment1.text()).toEqual('Other');
+
+    const assignment2 = component.find(ASSIGNMENT_ITEM(1)).hostNodes();
+    expect(assignment2.text()).toEqual(`fieldA${MULTI_FIELD_KEY_SEPARATOR}fieldB`);
+
+    const assignment3 = component.find(ASSIGNMENT_ITEM(2)).hostNodes();
+    expect(assignment3.text()).toEqual('(Empty)');
+
+    const assignment4 = component.find(ASSIGNMENT_ITEM(3)).hostNodes();
+    expect(assignment4.text()).toEqual('   with-whitespaces   ');
+  });
+});
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/categorical_color_mapping.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/categorical_color_mapping.tsx
new file mode 100644
index 0000000000000..290c549684f90
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/categorical_color_mapping.tsx
@@ -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 React from 'react';
+import { Provider } from 'react-redux';
+import { type EnhancedStore, configureStore } from '@reduxjs/toolkit';
+import { isEqual } from 'lodash';
+import { colorMappingReducer, updateModel } from './state/color_mapping';
+import { Container } from './components/container/container';
+import { ColorMapping } from './config';
+import { uiReducer } from './state/ui';
+
+/**
+ * A configuration object that is required to populate correctly the visible categories
+ * or the ranges in the CategoricalColorMapping component
+ */
+export type ColorMappingInputData =
+  | {
+      type: 'categories';
+      /** an ORDERED array of categories rendered in the visualization  */
+      categories: Array<string | string[]>;
+    }
+  | {
+      type: 'ranges';
+      min: number;
+      max: number;
+      bins: number;
+    };
+
+/**
+ * The props of the CategoricalColorMapping component
+ */
+export interface ColorMappingProps {
+  /** The initial color mapping model, usually coming from a the visualization saved object */
+  model: ColorMapping.Config;
+  /** A map of paletteId and palette configuration */
+  palettes: Map<string, ColorMapping.CategoricalPalette>;
+  /** A data description of what needs to be colored */
+  data: ColorMappingInputData;
+  /** Theme dark mode */
+  isDarkMode: boolean;
+  /** A map between original and formatted tokens used to handle special cases, like the Other bucket and the empty bucket */
+  specialTokens: Map<string, string>;
+  /** A function called at every change in the model */
+  onModelUpdate: (model: ColorMapping.Config) => void;
+}
+
+/**
+ * The React component for mapping categorical values to colors
+ */
+export class CategoricalColorMapping extends React.Component<ColorMappingProps> {
+  store: EnhancedStore<{ colorMapping: ColorMapping.Config }>;
+  unsubscribe: () => void;
+  constructor(props: ColorMappingProps) {
+    super(props);
+    // configure the store at mount time
+    this.store = configureStore({
+      preloadedState: {
+        colorMapping: props.model,
+      },
+      reducer: {
+        colorMapping: colorMappingReducer,
+        ui: uiReducer,
+      },
+    });
+    // subscribe to store changes to update external tools
+    this.unsubscribe = this.store.subscribe(() => {
+      this.props.onModelUpdate(this.store.getState().colorMapping);
+    });
+  }
+  componentWillUnmount() {
+    this.unsubscribe();
+  }
+  componentDidUpdate(prevProps: Readonly<ColorMappingProps>) {
+    if (!isEqual(prevProps.model, this.props.model)) {
+      this.store.dispatch(updateModel(this.props.model));
+    }
+  }
+  render() {
+    const { palettes, data, isDarkMode, specialTokens } = this.props;
+    return (
+      <Provider store={this.store}>
+        <Container
+          palettes={palettes}
+          data={data}
+          isDarkMode={isDarkMode}
+          specialTokens={specialTokens}
+        />
+      </Provider>
+    );
+  }
+}
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/color/color_handling.test.ts b/packages/kbn-coloring/src/shared_components/color_mapping/color/color_handling.test.ts
new file mode 100644
index 0000000000000..93896394daf41
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/color/color_handling.test.ts
@@ -0,0 +1,294 @@
+/*
+ * Copyright 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 {
+  DEFAULT_COLOR_MAPPING_CONFIG,
+  DEFAULT_NEUTRAL_PALETTE_INDEX,
+} from '../config/default_color_mapping';
+import { getColorFactory } from './color_handling';
+import { getPalette, AVAILABLE_PALETTES } from '../palettes';
+import {
+  EUIAmsterdamColorBlindPalette,
+  EUI_AMSTERDAM_PALETTE_COLORS,
+} from '../palettes/eui_amsterdam';
+import { NeutralPalette, NEUTRAL_COLOR_DARK, NEUTRAL_COLOR_LIGHT } from '../palettes/neutral';
+import { toHex } from './color_math';
+
+import { ColorMapping } from '../config';
+
+describe('Color mapping - color generation', () => {
+  const getPaletteFn = getPalette(AVAILABLE_PALETTES, NeutralPalette);
+  it('returns EUI light colors from default config', () => {
+    const colorFactory = getColorFactory(DEFAULT_COLOR_MAPPING_CONFIG, getPaletteFn, false, {
+      type: 'categories',
+      categories: ['catA', 'catB', 'catC'],
+    });
+    expect(colorFactory('catA')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[0]);
+    expect(colorFactory('catB')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[1]);
+    expect(colorFactory('catC')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[2]);
+    // if the category is not available in the `categories` list then a default neutral color is used
+    expect(colorFactory('not_available')).toBe(NEUTRAL_COLOR_LIGHT[DEFAULT_NEUTRAL_PALETTE_INDEX]);
+  });
+
+  it('returns max number of colors defined in palette, use other color otherwise', () => {
+    const twoColorPalette: ColorMapping.CategoricalPalette = {
+      id: 'twoColors',
+      name: 'twoColors',
+      colorCount: 2,
+      type: 'categorical',
+      getColor(valueInRange, isDarkMode) {
+        return ['red', 'blue'][valueInRange];
+      },
+    };
+
+    const simplifiedGetPaletteGn = getPalette(
+      new Map([[twoColorPalette.id, twoColorPalette]]),
+      NeutralPalette
+    );
+    const colorFactory = getColorFactory(
+      {
+        ...DEFAULT_COLOR_MAPPING_CONFIG,
+        paletteId: twoColorPalette.id,
+      },
+      simplifiedGetPaletteGn,
+      false,
+      {
+        type: 'categories',
+        categories: ['cat1', 'cat2', 'cat3', 'cat4'],
+      }
+    );
+    expect(colorFactory('cat1')).toBe('#ff0000');
+    expect(colorFactory('cat2')).toBe('#0000ff');
+    // return a palette color only up to the max number of color in the palette
+    expect(colorFactory('cat3')).toBe(NEUTRAL_COLOR_LIGHT[DEFAULT_NEUTRAL_PALETTE_INDEX]);
+    expect(colorFactory('cat4')).toBe(NEUTRAL_COLOR_LIGHT[DEFAULT_NEUTRAL_PALETTE_INDEX]);
+  });
+
+  // currently there is no difference in the two colors, but this could change in the future
+  // this test will catch the change
+  it('returns EUI dark colors from default config', () => {
+    const colorFactory = getColorFactory(DEFAULT_COLOR_MAPPING_CONFIG, getPaletteFn, true, {
+      type: 'categories',
+      categories: ['catA', 'catB', 'catC'],
+    });
+    expect(colorFactory('catA')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[0]);
+    expect(colorFactory('catB')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[1]);
+    expect(colorFactory('catC')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[2]);
+    // if the category is not available in the `categories` list then a default neutral color is used
+    expect(colorFactory('not_available')).toBe(NEUTRAL_COLOR_DARK[DEFAULT_NEUTRAL_PALETTE_INDEX]);
+  });
+
+  it('handles special tokens, multi-field categories and non-trimmed whitespaces', () => {
+    const colorFactory = getColorFactory(DEFAULT_COLOR_MAPPING_CONFIG, getPaletteFn, false, {
+      type: 'categories',
+      categories: ['__other__', ['fieldA', 'fieldB'], '__empty__', '   with-whitespaces   '],
+    });
+    expect(colorFactory('__other__')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[0]);
+    expect(colorFactory(['fieldA', 'fieldB'])).toBe(EUI_AMSTERDAM_PALETTE_COLORS[1]);
+    expect(colorFactory('__empty__')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[2]);
+    expect(colorFactory('   with-whitespaces   ')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[3]);
+  });
+
+  it('ignores configured assignments in auto mode', () => {
+    const colorFactory = getColorFactory(
+      {
+        ...DEFAULT_COLOR_MAPPING_CONFIG,
+        assignments: [
+          {
+            color: { type: 'colorCode', colorCode: 'red' },
+            rule: { type: 'matchExactly', values: ['assignmentToIgnore'] },
+            touched: false,
+          },
+        ],
+      },
+      getPaletteFn,
+      false,
+      {
+        type: 'categories',
+        categories: ['catA', 'catB', 'assignmentToIgnore'],
+      }
+    );
+    expect(colorFactory('catA')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[0]);
+    expect(colorFactory('catB')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[1]);
+    expect(colorFactory('assignmentToIgnore')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[2]);
+  });
+
+  it('color with auto rule are assigned in order of the configured data input', () => {
+    const colorFactory = getColorFactory(
+      {
+        ...DEFAULT_COLOR_MAPPING_CONFIG,
+        assignmentMode: 'manual',
+        assignments: [
+          {
+            color: { type: 'colorCode', colorCode: 'red' },
+            rule: { type: 'auto' },
+            touched: false,
+          },
+          {
+            color: { type: 'colorCode', colorCode: 'blue' },
+            rule: { type: 'matchExactly', values: ['blueCat'] },
+            touched: false,
+          },
+          {
+            color: { type: 'colorCode', colorCode: 'green' },
+            rule: { type: 'auto' },
+            touched: false,
+          },
+        ],
+      },
+      getPaletteFn,
+      false,
+      {
+        type: 'categories',
+        categories: ['blueCat', 'redCat', 'greenCat'],
+      }
+    );
+    // this matches exactly
+    expect(colorFactory('blueCat')).toBe('blue');
+    // this matches with the first availabe "auto" rule
+    expect(colorFactory('redCat')).toBe('red');
+    // this matches with the second availabe "auto" rule
+    expect(colorFactory('greenCat')).toBe('green');
+    // if the category is not available in the `categories` list then a default neutral color is used
+    expect(colorFactory('not_available')).toBe(NEUTRAL_COLOR_LIGHT[DEFAULT_NEUTRAL_PALETTE_INDEX]);
+  });
+
+  it('returns sequential gradient colors from darker to lighter [desc, lightMode]', () => {
+    const colorFactory = getColorFactory(
+      {
+        ...DEFAULT_COLOR_MAPPING_CONFIG,
+        colorMode: {
+          type: 'gradient',
+          steps: [
+            {
+              type: 'categorical',
+              paletteId: EUIAmsterdamColorBlindPalette.id,
+              colorIndex: 0,
+              touched: false,
+            },
+          ],
+          sort: 'desc',
+        },
+      },
+      getPaletteFn,
+      false,
+      {
+        type: 'categories',
+        categories: ['cat1', 'cat2', 'cat3'],
+      }
+    );
+    // this matches exactly with the initial step selected
+    expect(toHex(colorFactory('cat1'))).toBe(toHex(EUI_AMSTERDAM_PALETTE_COLORS[0]));
+    expect(toHex(colorFactory('cat2'))).toBe('#93cebc');
+    expect(toHex(colorFactory('cat3'))).toBe('#cce8e0');
+  });
+
+  it('returns sequential gradient colors from lighter to darker [asc, lightMode]', () => {
+    const colorFactory = getColorFactory(
+      {
+        ...DEFAULT_COLOR_MAPPING_CONFIG,
+        colorMode: {
+          type: 'gradient',
+          steps: [
+            {
+              type: 'categorical',
+              paletteId: EUIAmsterdamColorBlindPalette.id,
+              colorIndex: 0,
+              touched: false,
+            },
+          ],
+          sort: 'asc',
+        },
+      },
+      getPaletteFn,
+      false,
+      {
+        type: 'categories',
+        categories: ['cat1', 'cat2', 'cat3'],
+      }
+    );
+    expect(toHex(colorFactory('cat1'))).toBe('#cce8e0');
+    expect(toHex(colorFactory('cat2'))).toBe('#93cebc');
+    // this matches exactly with the initial step selected
+    expect(toHex(colorFactory('cat3'))).toBe(toHex(EUI_AMSTERDAM_PALETTE_COLORS[0]));
+  });
+
+  it('returns 2 colors gradient [desc, lightMode]', () => {
+    const colorFactory = getColorFactory(
+      {
+        ...DEFAULT_COLOR_MAPPING_CONFIG,
+        colorMode: {
+          type: 'gradient',
+          steps: [
+            {
+              type: 'categorical',
+              paletteId: EUIAmsterdamColorBlindPalette.id,
+              colorIndex: 0,
+              touched: false,
+            },
+            {
+              type: 'categorical',
+              paletteId: EUIAmsterdamColorBlindPalette.id,
+              colorIndex: 2,
+              touched: false,
+            },
+          ],
+          sort: 'desc',
+        },
+      },
+      getPaletteFn,
+      false,
+      {
+        type: 'categories',
+        categories: ['cat1', 'cat2', 'cat3'],
+      }
+    );
+    expect(toHex(colorFactory('cat1'))).toBe(toHex(EUI_AMSTERDAM_PALETTE_COLORS[0])); // EUI green
+    expect(toHex(colorFactory('cat2'))).toBe('#a4908f'); // red gray green
+    expect(toHex(colorFactory('cat3'))).toBe(toHex(EUI_AMSTERDAM_PALETTE_COLORS[2])); // EUI pink
+  });
+
+  it('returns divergent gradient [asc, darkMode]', () => {
+    const colorFactory = getColorFactory(
+      {
+        ...DEFAULT_COLOR_MAPPING_CONFIG,
+        colorMode: {
+          type: 'gradient',
+          steps: [
+            {
+              type: 'categorical',
+              paletteId: EUIAmsterdamColorBlindPalette.id,
+              colorIndex: 0,
+              touched: false,
+            },
+            { type: 'categorical', paletteId: NeutralPalette.id, colorIndex: 0, touched: false },
+            {
+              type: 'categorical',
+              paletteId: EUIAmsterdamColorBlindPalette.id,
+              colorIndex: 2,
+              touched: false,
+            },
+          ],
+          sort: 'asc', // testing in ascending order
+        },
+      },
+      getPaletteFn,
+      true, // testing in dark mode
+      {
+        type: 'categories',
+        categories: ['cat1', 'cat2', 'cat3'],
+      }
+    );
+    expect(toHex(colorFactory('cat1'))).toBe(toHex(EUI_AMSTERDAM_PALETTE_COLORS[2])); // EUI pink
+    expect(toHex(colorFactory('cat2'))).toBe(NEUTRAL_COLOR_DARK[0]); // NEUTRAL LIGHT GRAY
+    expect(toHex(colorFactory('cat3'))).toBe(toHex(EUI_AMSTERDAM_PALETTE_COLORS[0])); // EUI green
+    expect(toHex(colorFactory('not available cat'))).toBe(
+      toHex(NEUTRAL_COLOR_DARK[DEFAULT_NEUTRAL_PALETTE_INDEX])
+    ); // check the other
+  });
+});
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/color/color_handling.ts b/packages/kbn-coloring/src/shared_components/color_mapping/color/color_handling.ts
new file mode 100644
index 0000000000000..795f94b740e9b
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/color/color_handling.ts
@@ -0,0 +1,164 @@
+/*
+ * Copyright 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 chroma from 'chroma-js';
+import { ColorMapping } from '../config';
+import { changeAlpha, combineColors, getValidColor } from './color_math';
+import { generateAutoAssignmentsForCategories } from '../config/assignment_from_categories';
+import { getPalette } from '../palettes';
+import { ColorMappingInputData } from '../categorical_color_mapping';
+import { ruleMatch } from './rule_matching';
+import { GradientColorMode } from '../config/types';
+
+export function getAssignmentColor(
+  colorMode: ColorMapping.Config['colorMode'],
+  color: ColorMapping.Config['assignments'][number]['color'],
+  getPaletteFn: ReturnType<typeof getPalette>,
+  isDarkMode: boolean,
+  index: number,
+  total: number
+) {
+  switch (color.type) {
+    case 'colorCode':
+    case 'categorical':
+      return getColor(color, getPaletteFn, isDarkMode);
+    case 'gradient': {
+      if (colorMode.type === 'categorical') {
+        return 'red';
+      }
+      const colorScale = getGradientColorScale(colorMode, getPaletteFn, isDarkMode);
+      return total === 0 ? 'red' : total === 1 ? colorScale(0) : colorScale(index / (total - 1));
+    }
+  }
+}
+
+export function getColor(
+  color: ColorMapping.ColorCode | ColorMapping.CategoricalColor,
+  getPaletteFn: ReturnType<typeof getPalette>,
+  isDarkMode: boolean
+) {
+  return color.type === 'colorCode'
+    ? color.colorCode
+    : getValidColor(getPaletteFn(color.paletteId).getColor(color.colorIndex, isDarkMode)).hex();
+}
+
+export function getColorFactory(
+  model: ColorMapping.Config,
+  getPaletteFn: ReturnType<typeof getPalette>,
+  isDarkMode: boolean,
+  data: ColorMappingInputData
+): (category: string | string[]) => string {
+  const palette = getPaletteFn(model.paletteId);
+  // generate on-the-fly assignments in auto-mode based on current data.
+  // This simplify the code by always using assignments, even if there is no real static assigmnets
+  const assignments =
+    model.assignmentMode === 'auto'
+      ? generateAutoAssignmentsForCategories(data, palette, model.colorMode)
+      : model.assignments;
+
+  // find auto-assigned colors
+  const autoAssignedColors =
+    data.type === 'categories'
+      ? assignments.filter((a) => {
+          return (
+            a.rule.type === 'auto' || (a.rule.type === 'matchExactly' && a.rule.values.length === 0)
+          );
+        })
+      : [];
+
+  // find all categories that doesn't match with an assignment
+  const nonAssignedCategories =
+    data.type === 'categories'
+      ? data.categories.filter((category) => {
+          return !assignments.some(({ rule }) => ruleMatch(rule, category));
+        })
+      : [];
+
+  return (category: string | string[]) => {
+    if (typeof category === 'string' || Array.isArray(category)) {
+      const nonAssignedCategoryIndex = nonAssignedCategories.indexOf(category);
+
+      // return color for a non assigned category
+      if (nonAssignedCategoryIndex > -1) {
+        if (nonAssignedCategoryIndex < autoAssignedColors.length) {
+          const autoAssignmentIndex = assignments.findIndex(
+            (d) => d === autoAssignedColors[nonAssignedCategoryIndex]
+          );
+          return getAssignmentColor(
+            model.colorMode,
+            autoAssignedColors[nonAssignedCategoryIndex].color,
+            getPaletteFn,
+            isDarkMode,
+            autoAssignmentIndex,
+            assignments.length
+          );
+        }
+        // if no auto-assign color rule/color is available then use the other color
+        // TODO: the specialAssignment[0] position is arbitrary, we should fix it better
+        return getColor(model.specialAssignments[0].color, getPaletteFn, isDarkMode);
+      }
+
+      // find the assignment where the category matches the rule
+      const matchingAssignmentIndex = assignments.findIndex(({ rule }) => {
+        return ruleMatch(rule, category);
+      });
+
+      // return the assigned color
+      if (matchingAssignmentIndex > -1) {
+        const assignment = assignments[matchingAssignmentIndex];
+        return getAssignmentColor(
+          model.colorMode,
+          assignment.color,
+          getPaletteFn,
+          isDarkMode,
+          matchingAssignmentIndex,
+          assignments.length
+        );
+      }
+      // if no assign color rule/color is available then use the other color
+      // TODO: the specialAssignment[0] position is arbitrary, we should fix it better
+      return getColor(model.specialAssignments[0].color, getPaletteFn, isDarkMode);
+    } else {
+      const matchingAssignmentIndex = assignments.findIndex(({ rule }) => {
+        return ruleMatch(rule, category);
+      });
+
+      if (matchingAssignmentIndex > -1) {
+        const assignment = assignments[matchingAssignmentIndex];
+        return getAssignmentColor(
+          model.colorMode,
+          assignment.color,
+          getPaletteFn,
+          isDarkMode,
+          matchingAssignmentIndex,
+          assignments.length
+        );
+      }
+      return getColor(model.specialAssignments[0].color, getPaletteFn, isDarkMode);
+    }
+  };
+}
+
+export function getGradientColorScale(
+  colorMode: GradientColorMode,
+  getPaletteFn: ReturnType<typeof getPalette>,
+  isDarkMode: boolean
+): (value: number) => string {
+  const steps =
+    colorMode.steps.length === 1
+      ? [
+          getColor(colorMode.steps[0], getPaletteFn, isDarkMode),
+          combineColors(
+            changeAlpha(getColor(colorMode.steps[0], getPaletteFn, isDarkMode), 0.3),
+            isDarkMode ? 'black' : 'white'
+          ),
+        ]
+      : colorMode.steps.map((d) => getColor(d, getPaletteFn, isDarkMode));
+  steps.sort(() => (colorMode.sort === 'asc' ? -1 : 1));
+  const scale = chroma.scale(steps).mode('lab');
+  return (value: number) => scale(value).hex();
+}
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/color/color_math.ts b/packages/kbn-coloring/src/shared_components/color_mapping/color/color_math.ts
new file mode 100644
index 0000000000000..eb9e57d52af55
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/color/color_math.ts
@@ -0,0 +1,60 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import chroma from 'chroma-js';
+
+export function getValidColor(color: string): chroma.Color {
+  try {
+    return chroma(color);
+  } catch {
+    return chroma('red');
+  }
+}
+
+export function hasEnoughContrast(color: string, isDark: boolean, threshold = 4.5) {
+  return chroma.contrast(getValidColor(color), isDark ? 'black' : 'white') >= threshold;
+}
+
+export function changeAlpha(color: string, alpha: number) {
+  const [r, g, b] = getValidColor(color).rgb();
+  return `rgba(${r},${g},${b},${alpha})`;
+}
+
+export function toHex(color: string) {
+  return getValidColor(color).hex().toLowerCase();
+}
+
+export function isSameColor(color1: string, color2: string) {
+  return toHex(color1) === toHex(color2);
+}
+
+/**
+ * Blend a foreground (fg) color with a background (bg) color
+ */
+export function combineColors(fg: string, bg: string): string {
+  const [fgR, fgG, fgB, fgA] = getValidColor(fg).rgba();
+  const [bgR, bgG, bgB, bgA] = getValidColor(bg).rgba();
+
+  // combine colors only if foreground has transparency
+  if (fgA === 1) {
+    return chroma.rgb(fgR, fgG, fgB).hex();
+  }
+
+  // For reference on alpha calculations:
+  // https://en.wikipedia.org/wiki/Alpha_compositing
+  const alpha = fgA + bgA * (1 - fgA);
+
+  if (alpha === 0) {
+    return '#00000000';
+  }
+
+  const r = Math.round((fgR * fgA + bgR * bgA * (1 - fgA)) / alpha);
+  const g = Math.round((fgG * fgA + bgG * bgA * (1 - fgA)) / alpha);
+  const b = Math.round((fgB * fgA + bgB * bgA * (1 - fgA)) / alpha);
+  return `rgba(${r}, ${g}, ${b}, ${alpha})`;
+}
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/color/rule_matching.ts b/packages/kbn-coloring/src/shared_components/color_mapping/color/rule_matching.ts
new file mode 100644
index 0000000000000..7557644154a52
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/color/rule_matching.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 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 { ColorMapping } from '../config';
+
+export function ruleMatch(
+  rule: ColorMapping.Config['assignments'][number]['rule'],
+  value: string | number | string[]
+) {
+  switch (rule.type) {
+    case 'matchExactly':
+      if (Array.isArray(value)) {
+        return rule.values.some(
+          (v) =>
+            Array.isArray(v) && v.length === value.length && v.every((part, i) => part === value[i])
+        );
+      }
+      return rule.values.includes(`${value}`);
+    case 'matchExactlyCI':
+      return rule.values.some((d) => d.toLowerCase() === `${value}`);
+    case 'range':
+      // TODO: color by value not yet possible in all charts in elastic-charts
+      return typeof value === 'number' ? rangeMatch(rule, value) : false;
+    default:
+      return false;
+  }
+}
+
+export function rangeMatch(rule: ColorMapping.RuleRange, value: number) {
+  return (
+    (rule.min === rule.max && rule.min === value) ||
+    ((rule.minInclusive ? value >= rule.min : value > rule.min) &&
+      (rule.maxInclusive ? value <= rule.max : value < rule.max))
+  );
+}
+
+// TODO: move in some data/table related package
+export const SPECIAL_TOKENS_STRING_CONVERTION = new Map([
+  ['__other__', 'Other'],
+  ['', '(empty)'],
+]);
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/assignment.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/assignment.tsx
new file mode 100644
index 0000000000000..896f2ea392884
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/assignment.tsx
@@ -0,0 +1,150 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+import { useDispatch } from 'react-redux';
+import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { css } from '@emotion/react';
+import { euiThemeVars } from '@kbn/ui-theme';
+import {
+  removeAssignment,
+  updateAssignmentColor,
+  updateAssignmentRule,
+} from '../../state/color_mapping';
+import { ColorMapping } from '../../config';
+import { Range } from './range';
+import { Match } from './match';
+import { getPalette } from '../../palettes';
+
+import { ColorMappingInputData } from '../../categorical_color_mapping';
+import { ColorSwatch } from '../color_picker/color_swatch';
+
+export function Assignment({
+  data,
+  assignment,
+  disableDelete,
+  index,
+  total,
+  canPickColor,
+  editable,
+  palette,
+  colorMode,
+  getPaletteFn,
+  isDarkMode,
+  specialTokens,
+  assignmentValuesCounter,
+}: {
+  data: ColorMappingInputData;
+  index: number;
+  total: number;
+  colorMode: ColorMapping.Config['colorMode'];
+  assignment: ColorMapping.Config['assignments'][number];
+  disableDelete: boolean;
+  palette: ColorMapping.CategoricalPalette;
+  getPaletteFn: ReturnType<typeof getPalette>;
+  canPickColor: boolean;
+  editable: boolean;
+  isDarkMode: boolean;
+  specialTokens: Map<string, string>;
+  assignmentValuesCounter: Map<string | string[], number>;
+}) {
+  const dispatch = useDispatch();
+
+  return (
+    <EuiFlexGroup
+      direction="row"
+      gutterSize="s"
+      alignItems="flexStart"
+      justifyContent="spaceBetween"
+    >
+      <EuiFlexItem grow={0}>
+        <ColorSwatch
+          forType="assignment"
+          isDarkMode={isDarkMode}
+          swatchShape="square"
+          canPickColor={canPickColor}
+          colorMode={colorMode}
+          assignmentColor={assignment.color}
+          getPaletteFn={getPaletteFn}
+          index={index}
+          palette={palette}
+          total={total}
+          onColorChange={(color) => {
+            dispatch(updateAssignmentColor({ assignmentIndex: index, color }));
+          }}
+        />
+      </EuiFlexItem>
+
+      {assignment.rule.type === 'auto' ||
+      assignment.rule.type === 'matchExactly' ||
+      assignment.rule.type === 'matchExactlyCI' ? (
+        <Match
+          editable={editable}
+          index={index}
+          rule={assignment.rule}
+          options={data.type === 'categories' ? data.categories : []}
+          specialTokens={specialTokens}
+          updateValue={(values: Array<string | string[]>) => {
+            dispatch(
+              updateAssignmentRule({
+                assignmentIndex: index,
+                rule: values.length === 0 ? { type: 'auto' } : { type: 'matchExactly', values },
+              })
+            );
+          }}
+          assignmentValuesCounter={assignmentValuesCounter}
+        />
+      ) : assignment.rule.type === 'range' ? (
+        <Range
+          rule={assignment.rule}
+          editable={editable}
+          updateValue={(min, max, minInclusive, maxInclusive) => {
+            const rule: ColorMapping.RuleRange = {
+              type: 'range',
+              min,
+              max,
+              minInclusive,
+              maxInclusive,
+            };
+            dispatch(updateAssignmentRule({ assignmentIndex: index, rule }));
+          }}
+        />
+      ) : null}
+
+      <EuiFlexItem grow={0}>
+        <EuiButtonIcon
+          iconType="trash"
+          size="xs"
+          disabled={disableDelete}
+          onClick={() => dispatch(removeAssignment(index))}
+          aria-label={i18n.translate(
+            'coloring.colorMapping.assignments.deleteAssignmentButtonLabel',
+            {
+              defaultMessage: 'Delete this assignment',
+            }
+          )}
+          color="danger"
+          css={
+            !disableDelete
+              ? css`
+                  color: ${euiThemeVars.euiTextSubduedColor};
+                  transition: ${euiThemeVars.euiAnimSpeedFast} ease-in-out;
+                  transition-property: color;
+                  &:hover,
+                  &:focus {
+                    color: ${euiThemeVars.euiColorDangerText};
+                  }
+                `
+              : undefined
+          }
+        />
+      </EuiFlexItem>
+    </EuiFlexGroup>
+  );
+}
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/match.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/match.tsx
new file mode 100644
index 0000000000000..43c5583191cf3
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/match.tsx
@@ -0,0 +1,101 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+import { EuiComboBox, EuiFlexItem, EuiIcon } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { MULTI_FIELD_KEY_SEPARATOR } from '@kbn/data-plugin/common';
+import { euiThemeVars } from '@kbn/ui-theme';
+import { ColorMapping } from '../../config';
+
+export const Match: React.FC<{
+  index: number;
+  editable: boolean;
+  rule:
+    | ColorMapping.RuleAuto
+    | ColorMapping.RuleMatchExactly
+    | ColorMapping.RuleMatchExactlyCI
+    | ColorMapping.RuleRegExp;
+  updateValue: (values: Array<string | string[]>) => void;
+  options: Array<string | string[]>;
+  specialTokens: Map<unknown, string>;
+  assignmentValuesCounter: Map<string | string[], number>;
+}> = ({ index, rule, updateValue, editable, options, specialTokens, assignmentValuesCounter }) => {
+  const selectedOptions =
+    rule.type === 'auto'
+      ? []
+      : typeof rule.values === 'string'
+      ? [
+          {
+            label: rule.values,
+            value: rule.values,
+            append:
+              (assignmentValuesCounter.get(rule.values) ?? 0) > 1 ? (
+                <EuiIcon size="s" type="warning" color={euiThemeVars.euiColorWarningText} />
+              ) : undefined,
+          },
+        ]
+      : rule.values.map((value) => {
+          const ruleValues = Array.isArray(value) ? value : [value];
+          return {
+            label: ruleValues.map((v) => specialTokens.get(v) ?? v).join(MULTI_FIELD_KEY_SEPARATOR),
+            value,
+            append:
+              (assignmentValuesCounter.get(value) ?? 0) > 1 ? (
+                <EuiIcon size="s" type="warning" color={euiThemeVars.euiColorWarningText} />
+              ) : undefined,
+          };
+        });
+
+  const convertedOptions = options.map((value) => {
+    const ruleValues = Array.isArray(value) ? value : [value];
+    return {
+      label: ruleValues.map((v) => specialTokens.get(v) ?? v).join(MULTI_FIELD_KEY_SEPARATOR),
+      value,
+    };
+  });
+
+  return (
+    <EuiFlexItem style={{ minWidth: 1, width: 1 }}>
+      <EuiComboBox
+        data-test-subj={`lns-colorMapping-assignmentsItem${index}`}
+        isDisabled={!editable}
+        fullWidth={true}
+        aria-label={i18n.translate('coloring.colorMapping.assignments.autoAssignedTermAriaLabel', {
+          defaultMessage:
+            "This color will be automatically assigned to the first term that doesn't match with all the other assignments",
+        })}
+        placeholder={i18n.translate(
+          'coloring.colorMapping.assignments.autoAssignedTermPlaceholder',
+          {
+            defaultMessage: 'Auto assigned',
+          }
+        )}
+        options={convertedOptions}
+        selectedOptions={selectedOptions}
+        onChange={(changedOptions) => {
+          updateValue(
+            changedOptions.reduce<Array<string | string[]>>((acc, option) => {
+              if (option.value !== undefined) {
+                acc.push(option.value);
+              }
+              return acc;
+            }, [])
+          );
+        }}
+        onCreateOption={(label) => {
+          if (selectedOptions.findIndex((option) => option.label.toLowerCase() === label) === -1) {
+            updateValue([...selectedOptions, { label, value: label }].map((d) => d.value));
+          }
+        }}
+        isClearable={false}
+        compressed
+      />
+    </EuiFlexItem>
+  );
+};
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/range.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/range.tsx
new file mode 100644
index 0000000000000..70f2cf49609e0
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/range.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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+import { EuiButtonEmpty, EuiFieldNumber, EuiFlexItem } from '@elastic/eui';
+import { ColorMapping } from '../../config';
+
+export const Range: React.FC<{
+  rule: ColorMapping.RuleRange;
+  editable: boolean;
+  updateValue: (min: number, max: number, minInclusive: boolean, maxInclusive: boolean) => void;
+}> = ({ rule, updateValue, editable }) => {
+  const minValid = rule.min <= rule.max;
+  const maxValid = rule.max >= rule.min;
+
+  return (
+    <>
+      <EuiFlexItem>
+        <EuiFieldNumber
+          compressed
+          prepend={
+            <EuiButtonEmpty
+              size="xs"
+              onClick={() => updateValue(rule.min, rule.max, !rule.minInclusive, rule.maxInclusive)}
+            >
+              {rule.minInclusive ? 'GTE' : 'GT'}
+            </EuiButtonEmpty>
+          }
+          placeholder="min"
+          value={rule.min}
+          isInvalid={!minValid}
+          disabled={!editable}
+          onChange={(e) =>
+            updateValue(+e.currentTarget.value, rule.max, rule.minInclusive, rule.maxInclusive)
+          }
+          aria-label="The min value"
+        />
+      </EuiFlexItem>
+      <EuiFlexItem>
+        <EuiFieldNumber
+          compressed
+          isInvalid={!maxValid}
+          prepend={
+            <EuiButtonEmpty
+              size="xs"
+              onClick={() => updateValue(rule.min, rule.max, rule.minInclusive, !rule.maxInclusive)}
+            >
+              {rule.maxInclusive ? 'LTE' : 'LT'}
+            </EuiButtonEmpty>
+          }
+          placeholder="max"
+          disabled={!editable}
+          value={rule.max}
+          onChange={(e) =>
+            updateValue(rule.min, +e.currentTarget.value, rule.minInclusive, rule.maxInclusive)
+          }
+          aria-label="The max value"
+        />
+      </EuiFlexItem>
+    </>
+  );
+};
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/special_assignment.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/special_assignment.tsx
new file mode 100644
index 0000000000000..29ede59e37f41
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/special_assignment.tsx
@@ -0,0 +1,79 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { EuiFieldText, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { useDispatch } from 'react-redux';
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import { ColorMapping } from '../../config';
+import { getPalette } from '../../palettes';
+import { ColorSwatch } from '../color_picker/color_swatch';
+import { updateSpecialAssignmentColor } from '../../state/color_mapping';
+
+export function SpecialAssignment({
+  assignment,
+  index,
+  palette,
+  getPaletteFn,
+  isDarkMode,
+  total,
+}: {
+  isDarkMode: boolean;
+  index: number;
+  assignment: ColorMapping.Config['specialAssignments'][number];
+  palette: ColorMapping.CategoricalPalette;
+  getPaletteFn: ReturnType<typeof getPalette>;
+  total: number;
+}) {
+  const dispatch = useDispatch();
+  const canPickColor = true;
+  return (
+    <EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
+      <EuiFlexItem grow={0}>
+        <ColorSwatch
+          forType="specialAssignment"
+          canPickColor={canPickColor}
+          colorMode={{ type: 'categorical' }}
+          assignmentColor={assignment.color}
+          getPaletteFn={getPaletteFn}
+          index={index}
+          palette={palette}
+          total={total}
+          swatchShape="square"
+          isDarkMode={isDarkMode}
+          onColorChange={(color) => {
+            dispatch(
+              updateSpecialAssignmentColor({
+                assignmentIndex: index,
+                color,
+              })
+            );
+          }}
+        />
+      </EuiFlexItem>
+      <EuiFlexItem
+        style={{
+          marginRight: 32,
+        }}
+      >
+        <EuiFieldText
+          compressed
+          fullWidth
+          disabled={true}
+          placeholder={i18n.translate('coloring.colorMapping.assignments.unassignedPlaceholder', {
+            defaultMessage: 'Unassigned terms',
+          })}
+          aria-label={i18n.translate('coloring.colorMapping.assignments.unassignedAriaLabel', {
+            defaultMessage:
+              'Assign this color to every unassigned not described in the assignment list',
+          })}
+        />
+      </EuiFlexItem>
+    </EuiFlexGroup>
+  );
+}
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/color_picker.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/color_picker.tsx
new file mode 100644
index 0000000000000..e1e8a08aa6b22
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/color_picker.tsx
@@ -0,0 +1,117 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { useState } from 'react';
+import {
+  EuiButtonEmpty,
+  EuiPopoverTitle,
+  EuiTab,
+  EuiTabs,
+  EuiTitle,
+  EuiHorizontalRule,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { ColorMapping } from '../../config';
+import { getPalette } from '../../palettes';
+import { PaletteColors } from './palette_colors';
+import { RGBPicker } from './rgb_picker';
+import { NeutralPalette } from '../../palettes/neutral';
+
+export function ColorPicker({
+  palette,
+  getPaletteFn,
+  color,
+  close,
+  selectColor,
+  isDarkMode,
+  deleteStep,
+}: {
+  color: ColorMapping.CategoricalColor | ColorMapping.ColorCode;
+  getPaletteFn: ReturnType<typeof getPalette>;
+  palette: ColorMapping.CategoricalPalette;
+  isDarkMode: boolean;
+  close: () => void;
+  selectColor: (color: ColorMapping.CategoricalColor | ColorMapping.ColorCode) => void;
+  deleteStep?: () => void;
+}) {
+  const [tab, setTab] = useState(
+    color.type === 'categorical' &&
+      (color.paletteId === palette.id || color.paletteId === NeutralPalette.id)
+      ? 'palette'
+      : 'custom'
+  );
+
+  return (
+    <div style={{ width: 168 }}>
+      <EuiPopoverTitle
+        paddingSize="none"
+        style={{
+          borderBottom: 'none',
+        }}
+      >
+        <EuiTabs size="m" expand>
+          <EuiTab onClick={() => setTab('palette')} isSelected={tab === 'palette'}>
+            <EuiTitle size="xxxs">
+              <span>
+                {i18n.translate('coloring.colorMapping.colorPicker.paletteTabLabel', {
+                  defaultMessage: 'Colors',
+                })}
+              </span>
+            </EuiTitle>
+          </EuiTab>
+          <EuiTab onClick={() => setTab('custom')} isSelected={tab === 'custom'}>
+            <EuiTitle size="xxxs">
+              <span>
+                {i18n.translate('coloring.colorMapping.colorPicker.customTabLabel', {
+                  defaultMessage: 'Custom',
+                })}
+              </span>
+            </EuiTitle>
+          </EuiTab>
+        </EuiTabs>
+      </EuiPopoverTitle>
+      {tab === 'palette' ? (
+        <PaletteColors
+          color={color}
+          getPaletteFn={getPaletteFn}
+          palette={palette}
+          selectColor={selectColor}
+          isDarkMode={isDarkMode}
+        />
+      ) : (
+        <RGBPicker
+          color={color}
+          getPaletteFn={getPaletteFn}
+          isDarkMode={isDarkMode}
+          selectColor={selectColor}
+          palette={palette}
+          close={close}
+        />
+      )}
+      {deleteStep ? (
+        <>
+          <EuiHorizontalRule margin="xs" />
+          <EuiButtonEmpty
+            color="danger"
+            size="xs"
+            iconType="trash"
+            onClick={() => {
+              close();
+              deleteStep();
+            }}
+            style={{ paddingBottom: 8 }}
+          >
+            {i18n.translate('coloring.colorMapping.colorPicker.removeGradientColorButtonLabel', {
+              defaultMessage: 'Remove color step',
+            })}
+          </EuiButtonEmpty>
+        </>
+      ) : null}
+    </div>
+  );
+}
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/color_swatch.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/color_swatch.tsx
new file mode 100644
index 0000000000000..8ddc56d2476c7
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/color_swatch.tsx
@@ -0,0 +1,184 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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 {
+  EuiColorPickerSwatch,
+  EuiPopover,
+  euiShadowSmall,
+  isColorDark,
+  useEuiTheme,
+} from '@elastic/eui';
+import React from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { i18n } from '@kbn/i18n';
+import { css } from '@emotion/react';
+import { ColorPicker } from './color_picker';
+import { getAssignmentColor } from '../../color/color_handling';
+import { ColorMapping } from '../../config';
+import { getPalette } from '../../palettes';
+import { removeGradientColorStep } from '../../state/color_mapping';
+
+import { selectColorPickerVisibility } from '../../state/selectors';
+import { colorPickerVisibility, hideColorPickerVisibility } from '../../state/ui';
+import { getValidColor } from '../../color/color_math';
+
+interface ColorPickerSwatchProps {
+  colorMode: ColorMapping.Config['colorMode'];
+  assignmentColor:
+    | ColorMapping.Config['assignments'][number]['color']
+    | ColorMapping.Config['specialAssignments'][number]['color'];
+  getPaletteFn: ReturnType<typeof getPalette>;
+  canPickColor: boolean;
+  index: number;
+  total: number;
+  palette: ColorMapping.CategoricalPalette;
+  onColorChange: (color: ColorMapping.CategoricalColor | ColorMapping.ColorCode) => void;
+  swatchShape: 'square' | 'round';
+  isDarkMode: boolean;
+  forType: 'assignment' | 'specialAssignment' | 'gradient';
+}
+export const ColorSwatch = ({
+  colorMode,
+  assignmentColor,
+  getPaletteFn,
+  canPickColor,
+  index,
+  total,
+  palette,
+  onColorChange,
+  swatchShape,
+  isDarkMode,
+  forType,
+}: ColorPickerSwatchProps) => {
+  const colorPickerState = useSelector(selectColorPickerVisibility);
+  const dispatch = useDispatch();
+  const colorPickerVisible =
+    colorPickerState.index === index &&
+    colorPickerState.type === forType &&
+    colorPickerState.visibile;
+  const colorHex = getAssignmentColor(
+    colorMode,
+    assignmentColor,
+    getPaletteFn,
+    isDarkMode,
+    index,
+    total
+  );
+  const colorIsDark = isColorDark(...getValidColor(colorHex).rgb());
+  const euiTheme = useEuiTheme();
+  return canPickColor && assignmentColor.type !== 'gradient' ? (
+    <EuiPopover
+      panelPaddingSize="none"
+      isOpen={colorPickerVisible}
+      repositionOnScroll={true}
+      closePopover={() => dispatch(hideColorPickerVisibility())}
+      anchorPosition="upLeft"
+      button={
+        swatchShape === 'round' ? (
+          <button
+            aria-label={i18n.translate('coloring.colorMapping.colorPicker.pickAColorAriaLabel', {
+              defaultMessage: 'Pick a color',
+            })}
+            data-test-subj={`lns-colorMapping-colorSwatch-${index}`}
+            onClick={() =>
+              dispatch(
+                colorPickerVisible
+                  ? hideColorPickerVisibility()
+                  : colorPickerVisibility({ index, visible: true, type: forType })
+              )
+            }
+            css={css`
+              background: ${colorHex};
+              width: 16px;
+              height: 16px;
+              border-radius: 50%;
+              top: 8px;
+              border: 3px solid white;
+              ${euiShadowSmall(euiTheme)};
+              backgroundcolor: ${colorHex};
+              cursor: ${canPickColor ? 'pointer' : 'not-allowed'};
+            `}
+          />
+        ) : (
+          <EuiColorPickerSwatch
+            color={colorHex}
+            aria-label={i18n.translate('coloring.colorMapping.colorPicker.pickAColorAriaLabel', {
+              defaultMessage: 'Pick a color',
+            })}
+            data-test-subj={`lns-colorMapping-colorSwatch-${index}`}
+            onClick={() =>
+              dispatch(
+                colorPickerVisible
+                  ? hideColorPickerVisibility()
+                  : colorPickerVisibility({ index, visible: true, type: forType })
+              )
+            }
+            style={{
+              // the color swatch can't pickup colors written in rgb/css standard
+              backgroundColor: colorHex,
+              cursor: canPickColor ? 'pointer' : 'not-allowed',
+              width: 32,
+              height: 32,
+            }}
+            css={css`
+              &::after {
+                content: '';
+                width: 0;
+                height: 0;
+                border-left: 3px solid transparent;
+                border-right: 3px solid transparent;
+                border-top: 4px solid ${colorIsDark ? 'white' : 'black'};
+                margin: 0;
+                bottom: 2px;
+                position: absolute;
+                right: 2px;
+              }
+            `}
+          />
+        )
+      }
+    >
+      <ColorPicker
+        key={
+          assignmentColor.type === 'categorical'
+            ? `${assignmentColor.colorIndex}-${assignmentColor.paletteId}`
+            : assignmentColor.colorCode
+        }
+        color={assignmentColor}
+        palette={palette}
+        getPaletteFn={getPaletteFn}
+        close={() => dispatch(hideColorPickerVisibility())}
+        isDarkMode={isDarkMode}
+        selectColor={(color) => {
+          // dispatch update
+          onColorChange(color);
+        }}
+        deleteStep={
+          colorMode.type === 'gradient' && total > 1
+            ? () => dispatch(removeGradientColorStep(index))
+            : undefined
+        }
+      />
+    </EuiPopover>
+  ) : (
+    <EuiColorPickerSwatch
+      color={colorHex}
+      aria-label={i18n.translate('coloring.colorMapping.colorPicker.newColorAriaLabel', {
+        defaultMessage: 'Select a new color',
+      })}
+      disabled
+      style={{
+        // the color swatch can't pickup colors written in rgb/css standard
+        backgroundColor: colorHex,
+        cursor: 'not-allowed',
+        width: 32,
+        height: 32,
+      }}
+    />
+  );
+};
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/palette_colors.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/palette_colors.tsx
new file mode 100644
index 0000000000000..3cabd99469a35
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/palette_colors.tsx
@@ -0,0 +1,133 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import React from 'react';
+import {
+  EuiColorPickerSwatch,
+  EuiFlexGroup,
+  EuiFlexItem,
+  EuiHorizontalRule,
+  EuiIcon,
+  EuiText,
+  EuiToolTip,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { ColorMapping } from '../../config';
+import { getPalette } from '../../palettes';
+import { isSameColor } from '../../color/color_math';
+import { NeutralPalette } from '../../palettes/neutral';
+
+export function PaletteColors({
+  palette,
+  isDarkMode,
+  color,
+  getPaletteFn,
+  selectColor,
+}: {
+  palette: ColorMapping.CategoricalPalette;
+  isDarkMode: boolean;
+  color: ColorMapping.CategoricalColor | ColorMapping.ColorCode;
+  getPaletteFn: ReturnType<typeof getPalette>;
+  selectColor: (color: ColorMapping.CategoricalColor | ColorMapping.ColorCode) => void;
+}) {
+  const colors = Array.from({ length: palette.colorCount }, (d, i) => {
+    return palette.getColor(i, isDarkMode);
+  });
+  const neutralColors = Array.from({ length: NeutralPalette.colorCount }, (d, i) => {
+    return NeutralPalette.getColor(i, isDarkMode);
+  });
+  const originalColor =
+    color.type === 'categorical'
+      ? color.paletteId === NeutralPalette.id
+        ? NeutralPalette.getColor(color.colorIndex, isDarkMode)
+        : getPaletteFn(color.paletteId).getColor(color.colorIndex, isDarkMode)
+      : color.colorCode;
+  return (
+    <>
+      <EuiFlexGroup direction="column" style={{ padding: 8 }}>
+        <EuiFlexItem>
+          <EuiText size="s">
+            <strong>
+              {i18n.translate('coloring.colorMapping.colorPicker.paletteColorsLabel', {
+                defaultMessage: 'Palette colors',
+              })}
+            </strong>
+          </EuiText>
+          <EuiFlexGroup
+            direction="row"
+            gutterSize="s"
+            wrap={true}
+            alignItems="center"
+            justifyContent="flexStart"
+          >
+            {colors.map((c, index) => (
+              <EuiFlexItem key={c} grow={0}>
+                <EuiColorPickerSwatch
+                  data-test-subj={`lns-colorMapping-colorPicker-staticColor-${index}`}
+                  style={{
+                    border: isSameColor(c, originalColor) ? '2px solid black' : 'transparent',
+                  }}
+                  color={c}
+                  onClick={() =>
+                    selectColor({ type: 'categorical', paletteId: palette.id, colorIndex: index })
+                  }
+                />
+              </EuiFlexItem>
+            ))}
+          </EuiFlexGroup>
+        </EuiFlexItem>
+      </EuiFlexGroup>
+      <EuiHorizontalRule margin="xs" />
+      <EuiFlexGroup style={{ padding: 8, paddingTop: 0 }}>
+        <EuiFlexItem>
+          <EuiText size="s">
+            <strong>
+              {i18n.translate('coloring.colorMapping.colorPicker.themeAwareColorsLabel', {
+                defaultMessage: 'Neutral colors',
+              })}
+            </strong>
+            <EuiToolTip
+              position="bottom"
+              content={i18n.translate('coloring.colorMapping.colorPicker.themeAwareColorsTooltip', {
+                defaultMessage:
+                  'The provided neutral colors are theme-aware and will change appropriately when switching between light and dark themes',
+              })}
+            >
+              <EuiIcon tabIndex={0} type="questionInCircle" />
+            </EuiToolTip>
+          </EuiText>
+          <EuiFlexGroup
+            direction="row"
+            gutterSize="s"
+            wrap={true}
+            alignItems="center"
+            justifyContent="flexStart"
+          >
+            {neutralColors.map((c, index) => (
+              <EuiFlexItem key={c} grow={0}>
+                <EuiColorPickerSwatch
+                  style={{
+                    border: isSameColor(c, originalColor) ? '2px solid black' : 'transparent',
+                  }}
+                  data-test-subj={`lns-colorMapping-colorPicker-neutralColor-${index}`}
+                  color={c}
+                  onClick={() =>
+                    selectColor({
+                      type: 'categorical',
+                      paletteId: NeutralPalette.id,
+                      colorIndex: index,
+                    })
+                  }
+                />
+              </EuiFlexItem>
+            ))}
+          </EuiFlexGroup>
+        </EuiFlexItem>
+      </EuiFlexGroup>
+    </>
+  );
+}
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/rgb_picker.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/rgb_picker.tsx
new file mode 100644
index 0000000000000..63db994bb0d21
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/rgb_picker.tsx
@@ -0,0 +1,144 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { EuiColorPicker, EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiFormRow } from '@elastic/eui';
+import React, { useState } from 'react';
+import useDebounce from 'react-use/lib/useDebounce';
+import chromajs from 'chroma-js';
+import { css } from '@emotion/react';
+import { euiThemeVars } from '@kbn/ui-theme';
+import { ColorMapping } from '../../config';
+
+import { hasEnoughContrast } from '../../color/color_math';
+import { getPalette } from '../../palettes';
+
+export function RGBPicker({
+  isDarkMode,
+  color,
+  getPaletteFn,
+  selectColor,
+  close,
+}: {
+  palette: ColorMapping.CategoricalPalette;
+  isDarkMode: boolean;
+  color: ColorMapping.CategoricalColor | ColorMapping.ColorCode;
+  getPaletteFn: ReturnType<typeof getPalette>;
+  selectColor: (color: ColorMapping.CategoricalColor | ColorMapping.ColorCode) => void;
+  close: () => void;
+}) {
+  const [customColorMappingColor, setCustomColorMappingColor] = useState<
+    ColorMapping.CategoricalColor | ColorMapping.ColorCode
+  >(color);
+
+  const customColorHex =
+    customColorMappingColor.type === 'categorical'
+      ? getPaletteFn(customColorMappingColor.paletteId).getColor(
+          customColorMappingColor.colorIndex,
+          isDarkMode
+        )
+      : customColorMappingColor.colorCode;
+
+  const [colorTextInput, setColorTextInput] = useState<string>(customColorHex);
+
+  // check contrasts with WCAG 2.1 with a min contrast ratio of 3
+  const lightContrast = hasEnoughContrast(customColorHex, false, 3);
+  const darkContrast = hasEnoughContrast(customColorHex, true, 3);
+
+  const errorMessage = [
+    lightContrast === false ? 'light' : undefined,
+    darkContrast === false ? 'dark' : undefined,
+  ].filter(Boolean);
+
+  const isColorTextValid = chromajs.valid(colorTextInput);
+  const colorHasContrast = lightContrast && darkContrast;
+
+  // debounce setting the color from the rgb picker by 500ms
+  useDebounce(
+    () => {
+      if (color !== customColorMappingColor) {
+        selectColor(customColorMappingColor);
+      }
+    },
+    500,
+    [color, customColorMappingColor]
+  );
+  return (
+    <EuiFlexGroup direction="column" gutterSize="s" style={{ padding: 8 }}>
+      <EuiFlexItem>
+        <EuiColorPicker
+          onChange={(c) => {
+            setCustomColorMappingColor({
+              type: 'colorCode',
+              colorCode: c,
+            });
+            setColorTextInput(c);
+          }}
+          color={customColorHex}
+          display="inline"
+          swatches={[]}
+        />
+      </EuiFlexItem>
+      <EuiFlexItem>
+        <div
+          css={
+            !colorHasContrast && isColorTextValid
+              ? css`
+                  svg {
+                    fill: ${euiThemeVars.euiColorWarningText} !important;
+                  }
+                  input {
+                    background-image: linear-gradient(
+                      to top,
+                      ${euiThemeVars.euiColorWarning},
+                      ${euiThemeVars.euiColorWarning} 2px,
+                      transparent 2px,
+                      transparent 100%
+                    ) !important;
+                  }
+                  .euiFormErrorText {
+                    color: ${euiThemeVars.euiColorWarningText} !important;
+                  }
+                `
+              : undefined
+          }
+        >
+          <EuiFormRow
+            isInvalid={!isColorTextValid || !colorHasContrast}
+            error={
+              !isColorTextValid
+                ? `Please input a valid color hex code`
+                : !colorHasContrast
+                ? `This color has a low contrast in ${errorMessage} mode${
+                    errorMessage.length > 1 ? 's' : ''
+                  }`
+                : undefined
+            }
+          >
+            <EuiFieldText
+              placeholder="Please enter an hex color code"
+              value={colorTextInput}
+              compressed
+              isInvalid={!isColorTextValid || !colorHasContrast}
+              onChange={(e) => {
+                const textColor = e.currentTarget.value;
+                setColorTextInput(textColor);
+                if (chromajs.valid(textColor)) {
+                  setCustomColorMappingColor({
+                    type: 'colorCode',
+                    colorCode: chromajs(textColor).hex(),
+                  });
+                }
+              }}
+              aria-label="hex color input"
+            />
+          </EuiFormRow>
+        </div>
+      </EuiFlexItem>
+    </EuiFlexGroup>
+  );
+}
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/container/container.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/container/container.tsx
new file mode 100644
index 0000000000000..748f17fa45842
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/container/container.tsx
@@ -0,0 +1,252 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+import { useSelector, useDispatch } from 'react-redux';
+import {
+  EuiButtonEmpty,
+  EuiFlexGroup,
+  EuiFlexItem,
+  EuiFormLabel,
+  EuiHorizontalRule,
+  EuiPanel,
+  EuiSwitch,
+  EuiText,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { css } from '@emotion/react';
+import { Assignment } from '../assignment/assignment';
+import { SpecialAssignment } from '../assignment/special_assignment';
+import { PaletteSelector } from '../palette_selector/palette_selector';
+
+import {
+  RootState,
+  addNewAssignment,
+  assignAutomatically,
+  assignStatically,
+  changeGradientSortOrder,
+} from '../../state/color_mapping';
+import { generateAutoAssignmentsForCategories } from '../../config/assignment_from_categories';
+import { ColorMapping } from '../../config';
+import { getPalette } from '../../palettes';
+import { getUnusedColorForNewAssignment } from '../../config/assignments';
+import {
+  selectColorMode,
+  selectPalette,
+  selectSpecialAssignments,
+  selectIsAutoAssignmentMode,
+} from '../../state/selectors';
+import { ColorMappingInputData } from '../../categorical_color_mapping';
+import { Gradient } from '../palette_selector/gradient';
+import { NeutralPalette } from '../../palettes/neutral';
+
+export const MAX_ASSIGNABLE_COLORS = 10;
+
+function selectComputedAssignments(
+  data: ColorMappingInputData,
+  palette: ColorMapping.CategoricalPalette,
+  colorMode: ColorMapping.Config['colorMode']
+) {
+  return (state: RootState) =>
+    state.colorMapping.assignmentMode === 'auto'
+      ? generateAutoAssignmentsForCategories(data, palette, colorMode)
+      : state.colorMapping.assignments;
+}
+export function Container(props: {
+  palettes: Map<string, ColorMapping.CategoricalPalette>;
+  data: ColorMappingInputData;
+  isDarkMode: boolean;
+  /** map between original and formatted tokens used to handle special cases, like the Other bucket and the empty bucket */
+  specialTokens: Map<string, string>;
+}) {
+  const dispatch = useDispatch();
+
+  const getPaletteFn = getPalette(props.palettes, NeutralPalette);
+
+  const palette = useSelector(selectPalette(getPaletteFn));
+  const colorMode = useSelector(selectColorMode);
+  const autoAssignmentMode = useSelector(selectIsAutoAssignmentMode);
+  const assignments = useSelector(selectComputedAssignments(props.data, palette, colorMode));
+  const specialAssignments = useSelector(selectSpecialAssignments);
+
+  const canAddNewAssignment = !autoAssignmentMode && assignments.length < MAX_ASSIGNABLE_COLORS;
+
+  const assignmentValuesCounter = assignments.reduce<Map<string | string[], number>>(
+    (acc, assignment) => {
+      const values = assignment.rule.type === 'matchExactly' ? assignment.rule.values : [];
+      values.forEach((value) => {
+        acc.set(value, (acc.get(value) ?? 0) + 1);
+      });
+      return acc;
+    },
+    new Map()
+  );
+
+  return (
+    <EuiFlexGroup direction="column" gutterSize="s" justifyContent="flexStart">
+      <EuiFlexItem>
+        <PaletteSelector
+          palettes={props.palettes}
+          getPaletteFn={getPaletteFn}
+          isDarkMode={props.isDarkMode}
+        />
+      </EuiFlexItem>
+      <EuiFlexItem>
+        <EuiFlexGroup direction="row">
+          <EuiFlexItem>
+            <EuiFormLabel>
+              {i18n.translate('coloring.colorMapping.container.mappingAssignmentHeader', {
+                defaultMessage: 'Mapping assignments',
+              })}
+            </EuiFormLabel>
+          </EuiFlexItem>
+          <EuiFlexItem grow={0}>
+            <EuiSwitch
+              data-test-subj="lns-colorMapping-autoAssignSwitch"
+              label={
+                <EuiText size="xs">
+                  {i18n.translate('coloring.colorMapping.container.autoAssignLabel', {
+                    defaultMessage: 'Auto assign',
+                  })}
+                </EuiText>
+              }
+              checked={autoAssignmentMode}
+              compressed
+              onChange={() => {
+                if (autoAssignmentMode) {
+                  dispatch(assignStatically(assignments));
+                } else {
+                  dispatch(assignAutomatically());
+                }
+              }}
+            />
+          </EuiFlexItem>
+        </EuiFlexGroup>
+      </EuiFlexItem>
+      <EuiFlexItem>
+        <EuiPanel color="subdued" borderRadius="none" hasShadow={false} paddingSize="s">
+          <div
+            data-test-subj="lns-colorMapping-assignmentsList"
+            css={css`
+              display: grid;
+              grid-template-columns: ${colorMode.type === 'gradient' ? '[gradient] 16px' : ''} [assignment] auto;
+              gap: 8px;
+            `}
+          >
+            {colorMode.type !== 'gradient' ? null : (
+              <Gradient
+                colorMode={colorMode}
+                getPaletteFn={getPaletteFn}
+                isDarkMode={props.isDarkMode}
+                paletteId={palette.id}
+                assignmentsSize={assignments.length}
+              />
+            )}
+            {assignments.map((assignment, i) => {
+              return (
+                <div
+                  key={i}
+                  css={css`
+                    position: relative;
+                    grid-column: ${colorMode.type === 'gradient' ? 2 : 1};
+                    grid-row: ${i + 1};
+                    width: 100%;
+                  `}
+                >
+                  <Assignment
+                    data={props.data}
+                    index={i}
+                    total={assignments.length}
+                    colorMode={colorMode}
+                    editable={!autoAssignmentMode}
+                    canPickColor={!autoAssignmentMode && colorMode.type !== 'gradient'}
+                    palette={palette}
+                    isDarkMode={props.isDarkMode}
+                    getPaletteFn={getPaletteFn}
+                    assignment={assignment}
+                    disableDelete={assignments.length <= 1 || autoAssignmentMode}
+                    specialTokens={props.specialTokens}
+                    assignmentValuesCounter={assignmentValuesCounter}
+                  />
+                </div>
+              );
+            })}
+          </div>
+
+          <EuiHorizontalRule margin="xs" />
+          <EuiFlexGroup direction="row" gutterSize="s">
+            <EuiFlexItem data-test-subj="lns-colorMapping-specialAssignmentsList">
+              {props.data.type === 'categories' &&
+                specialAssignments.map((assignment, i) => {
+                  return (
+                    <SpecialAssignment
+                      key={i}
+                      index={i}
+                      palette={palette}
+                      isDarkMode={props.isDarkMode}
+                      getPaletteFn={getPaletteFn}
+                      assignment={assignment}
+                      total={specialAssignments.length}
+                    />
+                  );
+                })}
+            </EuiFlexItem>
+          </EuiFlexGroup>
+        </EuiPanel>
+        <EuiFlexGroup direction="row">
+          <EuiFlexItem style={{ display: 'block' }}>
+            <EuiButtonEmpty
+              data-test-subj="lns-colorMapping-addNewAssignment"
+              iconType="plusInCircleFilled"
+              size="xs"
+              flush="both"
+              onClick={() => {
+                dispatch(
+                  addNewAssignment({
+                    rule:
+                      props.data.type === 'categories'
+                        ? {
+                            type: 'matchExactly',
+                            values: [],
+                          }
+                        : { type: 'range', min: 0, max: 0, minInclusive: true, maxInclusive: true },
+                    color: getUnusedColorForNewAssignment(palette, colorMode, assignments),
+                    touched: false,
+                  })
+                );
+              }}
+              disabled={!canAddNewAssignment}
+              css={css`
+                margin-right: 8px;
+              `}
+            >
+              {i18n.translate('coloring.colorMapping.container.addAssignmentButtonLabel', {
+                defaultMessage: 'Add assignment',
+              })}
+            </EuiButtonEmpty>
+            {colorMode.type === 'gradient' && (
+              <EuiButtonEmpty
+                flush="both"
+                data-test-subj="lns-colorMapping-invertGradient"
+                iconType="sortable"
+                size="xs"
+                onClick={() => {
+                  dispatch(changeGradientSortOrder(colorMode.sort === 'asc' ? 'desc' : 'asc'));
+                }}
+              >
+                {i18n.translate('coloring.colorMapping.container.invertGradientButtonLabel', {
+                  defaultMessage: 'Invert gradient',
+                })}
+              </EuiButtonEmpty>
+            )}
+          </EuiFlexItem>
+        </EuiFlexGroup>
+      </EuiFlexItem>
+    </EuiFlexGroup>
+  );
+}
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient.tsx
new file mode 100644
index 0000000000000..c4e22f797deaa
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient.tsx
@@ -0,0 +1,336 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { euiFocusRing, EuiIcon, euiShadowSmall, useEuiTheme } from '@elastic/eui';
+import React from 'react';
+import { useDispatch } from 'react-redux';
+
+import { euiThemeVars } from '@kbn/ui-theme';
+import { css } from '@emotion/react';
+import { changeAlpha } from '../../color/color_math';
+
+import { ColorMapping } from '../../config';
+import { ColorSwatch } from '../color_picker/color_swatch';
+import { getPalette } from '../../palettes';
+
+import { addGradientColorStep, updateGradientColorStep } from '../../state/color_mapping';
+import { colorPickerVisibility } from '../../state/ui';
+import { getGradientColorScale } from '../../color/color_handling';
+
+export function Gradient({
+  paletteId,
+  colorMode,
+  getPaletteFn,
+  isDarkMode,
+  assignmentsSize,
+}: {
+  paletteId: string;
+  isDarkMode: boolean;
+  colorMode: ColorMapping.Config['colorMode'];
+  getPaletteFn: ReturnType<typeof getPalette>;
+  assignmentsSize: number;
+}) {
+  if (colorMode.type === 'categorical') {
+    return null;
+  }
+  const currentPalette = getPaletteFn(paletteId);
+  const gradientColorScale = getGradientColorScale(colorMode, getPaletteFn, isDarkMode);
+
+  const topMostColorStop =
+    colorMode.sort === 'asc'
+      ? colorMode.steps.length === 1
+        ? undefined
+        : colorMode.steps.at(-1)
+      : colorMode.steps.at(0);
+  const topMostColorStopIndex =
+    colorMode.sort === 'asc'
+      ? colorMode.steps.length === 1
+        ? NaN
+        : colorMode.steps.length - 1
+      : 0;
+
+  const bottomMostColorStop =
+    colorMode.sort === 'asc'
+      ? colorMode.steps.at(0)
+      : colorMode.steps.length === 1
+      ? undefined
+      : colorMode.steps.at(-1);
+  const bottomMostColorStopIndex =
+    colorMode.sort === 'asc' ? 0 : colorMode.steps.length === 1 ? NaN : colorMode.steps.length - 1;
+
+  const middleMostColorSep = colorMode.steps.length === 3 ? colorMode.steps[1] : undefined;
+  const middleMostColorStopIndex = colorMode.steps.length === 3 ? 1 : NaN;
+
+  return (
+    <>
+      {assignmentsSize > 1 && (
+        <div
+          className="gradientLine"
+          css={css`
+            position: relative;
+            grid-column: 1;
+            grid-row: 1;
+            width: 6px;
+            margin-left: 5px;
+            top: 16px;
+            height: calc(100% - 12px);
+            border-top-left-radius: 6px;
+            border-top-right-radius: 6px;
+            background-image: linear-gradient(
+              to bottom,
+              ${[gradientColorScale(0), gradientColorScale(1 / assignmentsSize)].join(',')}
+            );
+            border-left: 1px solid ${changeAlpha(euiThemeVars.euiColorDarkestShade, 0.2)};
+            border-top: 1px solid ${changeAlpha(euiThemeVars.euiColorDarkestShade, 0.2)};
+            border-right: 1px solid ${changeAlpha(euiThemeVars.euiColorDarkestShade, 0.2)};
+          `}
+        />
+      )}
+      <div
+        className="gradientStop"
+        css={css`
+          position: relative;
+          grid-column: 1;
+          grid-row: 1;
+          margin-top: 8px;
+        `}
+      >
+        {topMostColorStop ? (
+          <ColorStop
+            colorMode={colorMode}
+            step={topMostColorStop}
+            index={topMostColorStopIndex}
+            currentPalette={currentPalette}
+            getPaletteFn={getPaletteFn}
+            isDarkMode={isDarkMode}
+          />
+        ) : (
+          <AddStop colorMode={colorMode} currentPalette={currentPalette} at={1} />
+        )}
+      </div>
+
+      {assignmentsSize > 1 && (
+        <div
+          className="gradientLine"
+          css={css`
+            position: relative;
+            z-index: 1;
+            grid-column: 1;
+            grid-row-start: 2;
+            grid-row-end: ${assignmentsSize};
+            background-image: linear-gradient(
+              to bottom,
+              ${[
+                gradientColorScale(1 / assignmentsSize),
+                gradientColorScale((assignmentsSize - 1) / assignmentsSize),
+              ].join(',')}
+            );
+            margin: -4px 0;
+            width: 6px;
+            margin-left: 5px;
+            ${assignmentsSize === 2 ? 'height: 0;' : ''};
+            border-left: 1px solid ${changeAlpha(euiThemeVars.euiColorDarkestShade, 0.2)};
+            border-right: 1px solid ${changeAlpha(euiThemeVars.euiColorDarkestShade, 0.2)};
+          `}
+        >
+          <div
+            css={css`
+              position: absolute;
+              width: 16px;
+              height: 16px;
+              top: calc(50% - 5px);
+              margin-left: -6px;
+              margin-top: -3px;
+            `}
+          >
+            {middleMostColorSep ? (
+              <ColorStop
+                colorMode={colorMode}
+                step={middleMostColorSep}
+                index={middleMostColorStopIndex}
+                currentPalette={currentPalette}
+                getPaletteFn={getPaletteFn}
+                isDarkMode={isDarkMode}
+              />
+            ) : colorMode.steps.length === 2 ? (
+              <AddStop colorMode={colorMode} currentPalette={currentPalette} at={1} />
+            ) : undefined}
+          </div>
+        </div>
+      )}
+      {assignmentsSize > 1 && (
+        <div
+          className="gradientLine"
+          css={css`
+            position: relative;
+
+            grid-column: 1;
+            grid-row: ${assignmentsSize};
+            background-image: linear-gradient(
+              to bottom,
+
+              ${[
+                gradientColorScale((assignmentsSize - 1) / assignmentsSize),
+                gradientColorScale(1),
+              ].join(',')}
+            );
+            top: -4px;
+            height: 24px;
+            width: 6px;
+            margin-left: 5px;
+            border-bottom-left-radius: 6px;
+            border-bottom-right-radius: 6px;
+            border-left: 1px solid ${changeAlpha(euiThemeVars.euiColorDarkestShade, 0.2)};
+            border-bottom: 1px solid ${changeAlpha(euiThemeVars.euiColorDarkestShade, 0.2)};
+            border-right: 1px solid ${changeAlpha(euiThemeVars.euiColorDarkestShade, 0.2)};
+          `}
+        />
+      )}
+
+      <div
+        css={css`
+          position: relative;
+          grid-column: 1;
+          grid-row: ${assignmentsSize};
+          width: 16px;
+          height: 16px;
+          margin-top: 8px;
+        `}
+      >
+        {bottomMostColorStop ? (
+          <ColorStop
+            colorMode={colorMode}
+            step={bottomMostColorStop}
+            index={bottomMostColorStopIndex}
+            currentPalette={currentPalette}
+            getPaletteFn={getPaletteFn}
+            isDarkMode={isDarkMode}
+          />
+        ) : (
+          <AddStop colorMode={colorMode} currentPalette={currentPalette} at={1} />
+        )}
+      </div>
+    </>
+  );
+}
+
+function AddStop({
+  colorMode,
+  currentPalette,
+  at,
+}: {
+  colorMode: {
+    type: 'gradient';
+    steps: Array<(ColorMapping.CategoricalColor | ColorMapping.ColorCode) & { touched: boolean }>;
+  };
+  currentPalette: ColorMapping.CategoricalPalette;
+  at: number;
+}) {
+  const euiTheme = useEuiTheme();
+  const dispatch = useDispatch();
+  return (
+    <button
+      css={css`
+        position: relative;
+        border-radius: 50%;
+        width: 17px;
+        height: 17px;
+        padding: 0 0.5px;
+        ${euiFocusRing(euiTheme)};
+      `}
+      onClick={() => {
+        dispatch(
+          addGradientColorStep({
+            color: {
+              type: 'categorical',
+              // TODO assign the next available color or a better one
+              colorIndex: colorMode.steps.length,
+              paletteId: currentPalette.id,
+            },
+            at,
+          })
+        );
+        dispatch(
+          colorPickerVisibility({
+            index: at,
+            type: 'gradient',
+            visible: true,
+          })
+        );
+      }}
+    >
+      <div
+        css={css`
+          width: 15px;
+          height: 15px;
+          border-radius: 50%;
+          transition: 200ms background-color;
+          background-color: lightgrey;
+          &:hover {
+            background-color: #696f7d;
+          }
+          ${euiShadowSmall(euiTheme)}
+        `}
+      >
+        <EuiIcon
+          type="plus"
+          css={css`
+            position: absolute;
+            top: 0.5px;
+            left: 0;
+            transition: 200ms fill;
+            &:hover {
+              fill: white;
+            }
+          `}
+          color={'#696f7d'}
+        />
+      </div>
+    </button>
+  );
+}
+
+function ColorStop({
+  colorMode,
+  step,
+  index,
+  currentPalette,
+  getPaletteFn,
+  isDarkMode,
+}: {
+  colorMode: ColorMapping.GradientColorMode;
+  step: ColorMapping.CategoricalColor | ColorMapping.ColorCode;
+  index: number;
+  currentPalette: ColorMapping.CategoricalPalette;
+  getPaletteFn: ReturnType<typeof getPalette>;
+  isDarkMode: boolean;
+}) {
+  const dispatch = useDispatch();
+  return (
+    <ColorSwatch
+      canPickColor={true}
+      colorMode={colorMode}
+      assignmentColor={step}
+      getPaletteFn={getPaletteFn}
+      index={index}
+      palette={currentPalette}
+      total={colorMode.steps.length}
+      swatchShape="round"
+      isDarkMode={isDarkMode}
+      onColorChange={(color) => {
+        dispatch(
+          updateGradientColorStep({
+            index,
+            color,
+          })
+        );
+      }}
+      forType="gradient"
+    />
+  );
+}
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/palette_selector.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/palette_selector.tsx
new file mode 100644
index 0000000000000..a15bdca26ee1c
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/palette_selector.tsx
@@ -0,0 +1,263 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { useCallback, useState } from 'react';
+import { useSelector, useDispatch } from 'react-redux';
+import {
+  EuiButtonGroup,
+  EuiColorPalettePicker,
+  EuiConfirmModal,
+  EuiFlexGroup,
+  EuiFlexItem,
+  EuiFormRow,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { ScaleCategoricalIcon } from './scale_categorical';
+import { ScaleSequentialIcon } from './scale_sequential';
+
+import { RootState, updatePalette } from '../../state/color_mapping';
+import { ColorMapping } from '../../config';
+import { updateAssignmentsPalette, updateColorModePalette } from '../../config/assignments';
+import { getPalette } from '../../palettes';
+
+export function PaletteSelector({
+  palettes,
+  getPaletteFn,
+  isDarkMode,
+}: {
+  getPaletteFn: ReturnType<typeof getPalette>;
+  palettes: Map<string, ColorMapping.CategoricalPalette>;
+  isDarkMode: boolean;
+}) {
+  const dispatch = useDispatch();
+  const colorMode = useSelector((state: RootState) => state.colorMapping.colorMode);
+  const model = useSelector((state: RootState) => state.colorMapping);
+
+  const { paletteId } = model;
+
+  const switchPaletteFn = useCallback(
+    (selectedPaletteId: string, preserveColorChanges: boolean) => {
+      dispatch(
+        updatePalette({
+          paletteId: selectedPaletteId,
+          assignments: updateAssignmentsPalette(
+            model.assignments,
+            model.assignmentMode,
+            model.colorMode,
+            selectedPaletteId,
+            getPaletteFn,
+            preserveColorChanges
+          ),
+          colorMode: updateColorModePalette(
+            model.colorMode,
+            selectedPaletteId,
+            preserveColorChanges
+          ),
+        })
+      );
+    },
+    [getPaletteFn, model, dispatch]
+  );
+
+  const updateColorMode = useCallback(
+    (type: 'gradient' | 'categorical', preserveColorChanges: boolean) => {
+      const updatedColorMode: ColorMapping.Config['colorMode'] =
+        type === 'gradient'
+          ? {
+              type: 'gradient',
+              steps: [
+                {
+                  type: 'categorical',
+                  paletteId,
+                  colorIndex: 0,
+                  touched: false,
+                },
+              ],
+              sort: 'desc',
+            }
+          : { type: 'categorical' };
+
+      const assignments = updateAssignmentsPalette(
+        model.assignments,
+        model.assignmentMode,
+        updatedColorMode,
+        paletteId,
+        getPaletteFn,
+        preserveColorChanges
+      );
+      dispatch(updatePalette({ paletteId, assignments, colorMode: updatedColorMode }));
+    },
+    [getPaletteFn, model, dispatch, paletteId]
+  );
+
+  const [preserveModalPaletteId, setPreserveModalPaletteId] = useState<string | null>(null);
+
+  const preserveChangesModal =
+    preserveModalPaletteId !== null ? (
+      <EuiConfirmModal
+        title={i18n.translate('coloring.colorMapping.colorChangesModal.title', {
+          defaultMessage: 'Color changes detected',
+        })}
+        onCancel={() => {
+          if (preserveModalPaletteId) switchPaletteFn(preserveModalPaletteId, true);
+          setPreserveModalPaletteId(null);
+        }}
+        onConfirm={() => {
+          if (preserveModalPaletteId) switchPaletteFn(preserveModalPaletteId, false);
+          setPreserveModalPaletteId(null);
+        }}
+        confirmButtonText={i18n.translate('coloring.colorMapping.colorChangesModal.discardButton', {
+          defaultMessage: 'Discard changes',
+        })}
+        cancelButtonText={i18n.translate('coloring.colorMapping.colorChangesModal.preserveButton', {
+          defaultMessage: 'Preserve changes',
+        })}
+        buttonColor="danger"
+        defaultFocusedButton="confirm"
+      >
+        <p>
+          {i18n.translate('coloring.colorMapping.colorChangesModal.switchPaletteDescription', {
+            defaultMessage: 'Switching palette will discard all your custom color changes',
+          })}
+        </p>
+      </EuiConfirmModal>
+    ) : null;
+
+  const [colorScaleModalId, setColorScaleModalId] = useState<'gradient' | 'categorical' | null>(
+    null
+  );
+
+  const colorScaleModal =
+    colorScaleModalId !== null ? (
+      <EuiConfirmModal
+        title={i18n.translate('coloring.colorMapping.colorChangesModal.modalTitle', {
+          defaultMessage: 'Color changes detected',
+        })}
+        onCancel={() => {
+          setColorScaleModalId(null);
+        }}
+        onConfirm={() => {
+          if (colorScaleModalId) updateColorMode(colorScaleModalId, false);
+          setColorScaleModalId(null);
+        }}
+        cancelButtonText={i18n.translate(
+          'coloring.colorMapping.colorChangesModal.goBackButtonLabel',
+          {
+            defaultMessage: 'Go back',
+          }
+        )}
+        confirmButtonText={i18n.translate(
+          'coloring.colorMapping.colorChangesModal.discardButtonLabel',
+          {
+            defaultMessage: 'Discard changes',
+          }
+        )}
+        defaultFocusedButton="confirm"
+        buttonColor="danger"
+      >
+        <p>
+          {colorScaleModalId === 'categorical'
+            ? i18n.translate('coloring.colorMapping.colorChangesModal.categoricalModeDescription', {
+                defaultMessage: `Switching to a categorical mode will discard all your custom color changes`,
+              })
+            : i18n.translate('coloring.colorMapping.colorChangesModal.sequentialModeDescription', {
+                defaultMessage: `Switching to a sequential mode will discard all your custom color changes`,
+              })}
+        </p>
+      </EuiConfirmModal>
+    ) : null;
+
+  return (
+    <>
+      {preserveChangesModal}
+      {colorScaleModal}
+      <EuiFlexGroup direction="row">
+        <EuiFlexItem>
+          <EuiFormRow
+            label={i18n.translate('coloring.colorMapping.paletteSelector.paletteLabel', {
+              defaultMessage: `Color palette`,
+            })}
+          >
+            <EuiColorPalettePicker
+              data-test-subj="kbnColoring_ColorMapping_PalettePicker"
+              palettes={[...palettes.values()]
+                .filter((d) => d.name !== 'Neutral')
+                .map((palette) => ({
+                  'data-test-subj': `kbnColoring_ColorMapping_Palette-${palette.id}`,
+                  value: palette.id,
+                  title: palette.name,
+                  palette: Array.from({ length: palette.colorCount }, (_, i) => {
+                    return palette.getColor(i, isDarkMode);
+                  }),
+                  type: 'fixed',
+                }))}
+              onChange={(selectedPaletteId) => {
+                const hasChanges = model.assignments.some((a) => a.touched);
+                const hasGradientChanges =
+                  model.colorMode.type === 'gradient' &&
+                  model.colorMode.steps.some((a) => a.touched);
+                if (hasChanges || hasGradientChanges) {
+                  setPreserveModalPaletteId(selectedPaletteId);
+                } else {
+                  switchPaletteFn(selectedPaletteId, false);
+                }
+              }}
+              valueOfSelected={model.paletteId}
+              selectionDisplay={'palette'}
+              compressed={true}
+            />
+          </EuiFormRow>
+        </EuiFlexItem>
+        <EuiFlexItem grow={0}>
+          <EuiFormRow
+            label={i18n.translate('coloring.colorMapping.paletteSelector.scaleLabel', {
+              defaultMessage: `Scale`,
+            })}
+          >
+            <EuiButtonGroup
+              legend="Scale"
+              data-test-subj="lns_colorMapping_scaleSwitch"
+              options={[
+                {
+                  id: `categorical`,
+                  label: i18n.translate('coloring.colorMapping.paletteSelector.categoricalLabel', {
+                    defaultMessage: `Categorical`,
+                  }),
+                  iconType: ScaleCategoricalIcon,
+                },
+                {
+                  id: `gradient`,
+                  label: i18n.translate('coloring.colorMapping.paletteSelector.sequentialLabel', {
+                    defaultMessage: `Sequential`,
+                  }),
+                  iconType: ScaleSequentialIcon,
+                },
+              ]}
+              isFullWidth
+              buttonSize="compressed"
+              idSelected={colorMode.type}
+              onChange={(id) => {
+                const hasChanges = model.assignments.some((a) => a.touched);
+                const hasGradientChanges =
+                  model.colorMode.type === 'gradient' &&
+                  model.colorMode.steps.some((a) => a.touched);
+
+                if (hasChanges || hasGradientChanges) {
+                  setColorScaleModalId(id as 'gradient' | 'categorical');
+                } else {
+                  updateColorMode(id as 'gradient' | 'categorical', false);
+                }
+              }}
+              isIconOnly
+            />
+          </EuiFormRow>
+        </EuiFlexItem>
+      </EuiFlexGroup>
+    </>
+  );
+}
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/scale_categorical.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/scale_categorical.tsx
new file mode 100644
index 0000000000000..f71ed74485365
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/scale_categorical.tsx
@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import React from 'react';
+
+export function ScaleCategoricalIcon() {
+  return (
+    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
+      <path d="M4 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm2 2a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM5 6a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm4-2a1 1 0 1 1-2 0 1 1 0 0 1 2 0Zm2 2a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" />
+      <path d="M8 1a7 7 0 0 0 0 14h2a2 2 0 1 0 0-4 1 1 0 1 1 0-2h3.98C14.515 9 15 8.583 15 8a7 7 0 0 0-7-7ZM2 8a6 6 0 0 1 12-.005.035.035 0 0 1-.02.005H10a2 2 0 1 0 0 4 1 1 0 1 1 0 2H8a6 6 0 0 1-6-6Z" />
+    </svg>
+  );
+}
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/scale_sequential.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/scale_sequential.tsx
new file mode 100644
index 0000000000000..ec245f471f307
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/scale_sequential.tsx
@@ -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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+
+export function ScaleSequentialIcon() {
+  return (
+    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
+      <path
+        fillRule="evenodd"
+        d="M1 2a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2Zm4 0h1v12H5V2Zm3 12V2h2v12H8Zm3 0h3V2h-3v12Z"
+      />
+    </svg>
+  );
+}
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/config/assignment_from_categories.ts b/packages/kbn-coloring/src/shared_components/color_mapping/config/assignment_from_categories.ts
new file mode 100644
index 0000000000000..97c4d17c35e4d
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/config/assignment_from_categories.ts
@@ -0,0 +1,65 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { ColorMapping } from '.';
+import { ColorMappingInputData } from '../categorical_color_mapping';
+import { MAX_ASSIGNABLE_COLORS } from '../components/container/container';
+
+export function generateAutoAssignmentsForCategories(
+  data: ColorMappingInputData,
+  palette: ColorMapping.CategoricalPalette,
+  colorMode: ColorMapping.Config['colorMode']
+): ColorMapping.Config['assignments'] {
+  const isCategorical = colorMode.type === 'categorical';
+
+  const maxColorAssignable = data.type === 'categories' ? data.categories.length : data.bins;
+
+  const assignableColors = isCategorical
+    ? Math.min(palette.colorCount, maxColorAssignable)
+    : Math.min(MAX_ASSIGNABLE_COLORS, maxColorAssignable);
+
+  const autoRules: Array<ColorMapping.RuleMatchExactly | ColorMapping.RuleRange> =
+    data.type === 'categories'
+      ? data.categories.map((c) => ({ type: 'matchExactly', values: [c] }))
+      : Array.from({ length: data.bins }, (d, i) => {
+          const step = (data.max - data.min) / data.bins;
+          return {
+            type: 'range',
+            min: data.max - i * step - step,
+            max: data.max - i * step,
+            minInclusive: true,
+            maxInclusive: false,
+          };
+        });
+
+  const assignments = autoRules
+    .slice(0, assignableColors)
+    .map<ColorMapping.Config['assignments'][number]>((rule, colorIndex) => {
+      if (isCategorical) {
+        return {
+          rule,
+          color: {
+            type: 'categorical',
+            paletteId: palette.id,
+            colorIndex,
+          },
+          touched: false,
+        };
+      } else {
+        return {
+          rule,
+          color: {
+            type: 'gradient',
+          },
+          touched: false,
+        };
+      }
+    });
+
+  return assignments;
+}
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/config/assignments.ts b/packages/kbn-coloring/src/shared_components/color_mapping/config/assignments.ts
new file mode 100644
index 0000000000000..701baa1b1710b
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/config/assignments.ts
@@ -0,0 +1,91 @@
+/*
+ * Copyright 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 { ColorMapping } from '.';
+import { MAX_ASSIGNABLE_COLORS } from '../components/container/container';
+import { getPalette, NeutralPalette } from '../palettes';
+import { DEFAULT_NEUTRAL_PALETTE_INDEX } from './default_color_mapping';
+
+export function updateAssignmentsPalette(
+  assignments: ColorMapping.Config['assignments'],
+  assignmentMode: ColorMapping.Config['assignmentMode'],
+  colorMode: ColorMapping.Config['colorMode'],
+  paletteId: string,
+  getPaletteFn: ReturnType<typeof getPalette>,
+  preserveColorChanges: boolean
+): ColorMapping.Config['assignments'] {
+  const palette = getPaletteFn(paletteId);
+  const maxColors = palette.type === 'categorical' ? palette.colorCount : MAX_ASSIGNABLE_COLORS;
+  return assignmentMode === 'auto'
+    ? []
+    : assignments.map(({ rule, color, touched }, index) => {
+        if (preserveColorChanges && touched) {
+          return { rule, color, touched };
+        } else {
+          const newColor: ColorMapping.Config['assignments'][number]['color'] =
+            colorMode.type === 'categorical'
+              ? {
+                  type: 'categorical',
+                  paletteId: index < maxColors ? paletteId : NeutralPalette.id,
+                  colorIndex: index < maxColors ? index : 0,
+                }
+              : { type: 'gradient' };
+          return {
+            rule,
+            color: newColor,
+            touched: false,
+          };
+        }
+      });
+}
+
+export function updateColorModePalette(
+  colorMode: ColorMapping.Config['colorMode'],
+  paletteId: string,
+  preserveColorChanges: boolean
+): ColorMapping.Config['colorMode'] {
+  return colorMode.type === 'categorical'
+    ? colorMode
+    : {
+        type: 'gradient',
+        steps: colorMode.steps.map((step, stepIndex) => {
+          return preserveColorChanges
+            ? step
+            : { type: 'categorical', paletteId, colorIndex: stepIndex, touched: false };
+        }),
+        sort: colorMode.sort,
+      };
+}
+
+export function getUnusedColorForNewAssignment(
+  palette: ColorMapping.CategoricalPalette,
+  colorMode: ColorMapping.Config['colorMode'],
+  assignments: ColorMapping.Config['assignments']
+): ColorMapping.Config['assignments'][number]['color'] {
+  if (colorMode.type === 'categorical') {
+    // TODO: change the type of color assignment depending on palette
+    // compute the next unused color index in the palette.
+    const maxColors = palette.type === 'categorical' ? palette.colorCount : MAX_ASSIGNABLE_COLORS;
+    const colorIndices = new Set(Array.from({ length: maxColors }, (d, i) => i));
+    assignments.forEach(({ color }) => {
+      if (color.type === 'categorical' && color.paletteId === palette.id) {
+        colorIndices.delete(color.colorIndex);
+      }
+    });
+    const paletteForNextUnusedColorIndex = colorIndices.size > 0 ? palette.id : NeutralPalette.id;
+    const nextUnusedColorIndex =
+      colorIndices.size > 0 ? [...colorIndices][0] : DEFAULT_NEUTRAL_PALETTE_INDEX;
+    return {
+      type: 'categorical',
+      paletteId: paletteForNextUnusedColorIndex,
+      colorIndex: nextUnusedColorIndex,
+    };
+  } else {
+    return { type: 'gradient' };
+  }
+}
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/config/default_color_mapping.ts b/packages/kbn-coloring/src/shared_components/color_mapping/config/default_color_mapping.ts
new file mode 100644
index 0000000000000..e4005770b2883
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/config/default_color_mapping.ts
@@ -0,0 +1,79 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { ColorMapping } from '.';
+import { AVAILABLE_PALETTES, getPalette } from '../palettes';
+import { EUIAmsterdamColorBlindPalette } from '../palettes/eui_amsterdam';
+import { NeutralPalette } from '../palettes/neutral';
+import { getColor, getGradientColorScale } from '../color/color_handling';
+
+export const DEFAULT_NEUTRAL_PALETTE_INDEX = 1;
+
+/**
+ * The default color mapping used in Kibana, starts with the EUI color palette
+ */
+export const DEFAULT_COLOR_MAPPING_CONFIG: ColorMapping.Config = {
+  assignmentMode: 'auto',
+  assignments: [],
+  specialAssignments: [
+    {
+      rule: {
+        type: 'other',
+      },
+      color: {
+        type: 'categorical',
+        paletteId: NeutralPalette.id,
+        colorIndex: DEFAULT_NEUTRAL_PALETTE_INDEX,
+      },
+      touched: false,
+    },
+  ],
+  paletteId: EUIAmsterdamColorBlindPalette.id,
+  colorMode: {
+    type: 'categorical',
+  },
+};
+
+export function getPaletteColors(
+  isDarkMode: boolean,
+  colorMappings?: ColorMapping.Config
+): string[] {
+  const colorMappingModel = colorMappings ?? { ...DEFAULT_COLOR_MAPPING_CONFIG };
+  const palette = getPalette(AVAILABLE_PALETTES, NeutralPalette)(colorMappingModel.paletteId);
+  return Array.from({ length: palette.colorCount }, (d, i) => palette.getColor(i, isDarkMode));
+}
+
+export function getColorsFromMapping(
+  isDarkMode: boolean,
+  colorMappings?: ColorMapping.Config
+): string[] {
+  const { colorMode, paletteId, assignmentMode, assignments, specialAssignments } =
+    colorMappings ?? {
+      ...DEFAULT_COLOR_MAPPING_CONFIG,
+    };
+
+  const getPaletteFn = getPalette(AVAILABLE_PALETTES, NeutralPalette);
+  if (colorMode.type === 'gradient') {
+    const colorScale = getGradientColorScale(colorMode, getPaletteFn, isDarkMode);
+    return Array.from({ length: 6 }, (d, i) => colorScale(i / 6));
+  } else {
+    const palette = getPaletteFn(paletteId);
+    if (assignmentMode === 'auto') {
+      return Array.from({ length: palette.colorCount }, (d, i) => palette.getColor(i, isDarkMode));
+    } else {
+      return [
+        ...assignments.map((a) => {
+          return a.color.type === 'gradient' ? '' : getColor(a.color, getPaletteFn, isDarkMode);
+        }),
+        ...specialAssignments.map((a) => {
+          return getColor(a.color, getPaletteFn, isDarkMode);
+        }),
+      ].filter((color) => color !== '');
+    }
+  }
+}
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/config/index.ts b/packages/kbn-coloring/src/shared_components/color_mapping/config/index.ts
new file mode 100644
index 0000000000000..e75687596789e
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/config/index.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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 * as ColorMapping from './types';
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/config/types.ts b/packages/kbn-coloring/src/shared_components/color_mapping/config/types.ts
new file mode 100644
index 0000000000000..59cb18435112d
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/config/types.ts
@@ -0,0 +1,153 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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.
+ */
+
+/**
+ * A color specified as a CSS color datatype (rgb/a,hex,keywords,lab,lch etc)
+ */
+export interface ColorCode {
+  type: 'colorCode';
+  colorCode: string;
+}
+
+/**
+ * An index specified categorical color, coming from paletteId
+ */
+export interface CategoricalColor {
+  type: 'categorical';
+  paletteId: string;
+  colorIndex: number;
+}
+
+/**
+ * Specify that the Color in an Assignment needs to be taken from a gradient defined in the `Config.colorMode`
+ */
+export interface GradientColor {
+  type: 'gradient';
+}
+
+/**
+ * A special rule that match automatically, in order, all the categories that are not matching a specified rule
+ */
+export interface RuleAuto {
+  /* tag */
+  type: 'auto';
+}
+/**
+ * A rule that match exactly, case sensitive, with the provided strings
+ */
+export interface RuleMatchExactly {
+  /* tag */
+  type: 'matchExactly';
+  values: Array<string | string[]>;
+}
+
+/**
+ * A Match rule to match the values case insensitive
+ * @ignore not used yet
+ */
+export interface RuleMatchExactlyCI {
+  /* tag */
+  type: 'matchExactlyCI';
+  values: string[];
+}
+
+/**
+ * A range rule, not used yet, but can be used for numerical data assignments
+ */
+export interface RuleRange {
+  /* tag */
+  type: 'range';
+  /**
+   * The min value of the range
+   */
+  min: number;
+  /**
+   * The max value of the range
+   */
+  max: number;
+  /**
+   * `true` if the range is left-closed (the `min` value is considered within the range), false otherwise (only values that are
+   * greater than the `min` are considered within the range)
+   */
+  minInclusive: boolean;
+  /**
+   * `true` if the range is right-closed (the `max` value is considered within the range), false otherwise (only values less than
+   * the `max` are considered within the range)
+   */
+  maxInclusive: boolean;
+}
+/**
+ * Regex rule.
+ * @ignore not used yet
+ */
+export interface RuleRegExp {
+  /* tag */
+  type: 'regex';
+  /**
+   * TODO: not sure how we can store a regexp
+   */
+  values: string;
+}
+
+/**
+ * A specific catch-everything-else rule
+ */
+export interface RuleOthers {
+  /* tag */
+  type: 'other';
+}
+
+/**
+ * An assignment is the connection link between a rule and a color
+ */
+export interface Assignment<R, C> {
+  /**
+   * Describe the rule used to assign the color.
+   */
+  rule: R;
+  /**
+   * The color definition
+   */
+  color: C;
+
+  /**
+   * Specify if the color was changed from the original one
+   * TODO: rename
+   */
+  touched: boolean;
+}
+
+export interface CategoricalColorMode {
+  type: 'categorical';
+}
+export interface GradientColorMode {
+  type: 'gradient';
+  steps: Array<(CategoricalColor | ColorCode) & { touched: boolean }>;
+  sort: 'asc' | 'desc';
+}
+
+export interface Config {
+  paletteId: string;
+  colorMode: CategoricalColorMode | GradientColorMode;
+  assignmentMode: 'auto' | 'manual';
+  assignments: Array<
+    Assignment<
+      RuleAuto | RuleMatchExactly | RuleMatchExactlyCI | RuleRange | RuleRegExp,
+      CategoricalColor | ColorCode | GradientColor
+    >
+  >;
+  specialAssignments: Array<Assignment<RuleOthers, CategoricalColor | ColorCode>>;
+}
+
+export interface CategoricalPalette {
+  id: string;
+  name: string;
+  type: 'categorical';
+  colorCount: number;
+  getColor: (valueInRange: number, isDarkMode: boolean) => string;
+}
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/index.ts b/packages/kbn-coloring/src/shared_components/color_mapping/index.ts
new file mode 100644
index 0000000000000..1b49a2c6a8bf3
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/index.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export { CategoricalColorMapping, type ColorMappingProps } from './categorical_color_mapping';
+export type { ColorMappingInputData } from './categorical_color_mapping';
+export type { ColorMapping } from './config';
+export * from './palettes';
+export * from './color/color_handling';
+export { SPECIAL_TOKENS_STRING_CONVERTION } from './color/rule_matching';
+export {
+  DEFAULT_COLOR_MAPPING_CONFIG,
+  getPaletteColors,
+  getColorsFromMapping,
+} from './config/default_color_mapping';
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/palettes/elastic_brand.ts b/packages/kbn-coloring/src/shared_components/color_mapping/palettes/elastic_brand.ts
new file mode 100644
index 0000000000000..d93440c5ac5e4
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/palettes/elastic_brand.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 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 { ColorMapping } from '../config';
+
+export const ELASTIC_BRAND_PALETTE_COLORS = [
+  '#20377d',
+  '#7de2d1',
+  '#ff957d',
+  '#f04e98',
+  '#0077cc',
+  '#fec514',
+];
+
+export const ElasticBrandPalette: ColorMapping.CategoricalPalette = {
+  id: 'elastic_brand_2023',
+  name: 'Elastic Brand',
+  colorCount: ELASTIC_BRAND_PALETTE_COLORS.length,
+  type: 'categorical',
+  getColor(valueInRange) {
+    return ELASTIC_BRAND_PALETTE_COLORS[valueInRange];
+  },
+};
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/palettes/eui_amsterdam.ts b/packages/kbn-coloring/src/shared_components/color_mapping/palettes/eui_amsterdam.ts
new file mode 100644
index 0000000000000..ec48793e12819
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/palettes/eui_amsterdam.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 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 { ColorMapping } from '../config';
+
+export const EUI_AMSTERDAM_PALETTE_COLORS = [
+  '#54b399',
+  '#6092c0',
+  '#d36086',
+  '#9170b8',
+  '#ca8eae',
+  '#d6bf57',
+  '#b9a888',
+  '#da8b45',
+  '#aa6556',
+  '#e7664c',
+];
+
+export const EUIAmsterdamColorBlindPalette: ColorMapping.CategoricalPalette = {
+  id: 'eui_amsterdam_color_blind',
+  name: 'Default',
+  colorCount: EUI_AMSTERDAM_PALETTE_COLORS.length,
+  type: 'categorical',
+  getColor(valueInRange) {
+    return EUI_AMSTERDAM_PALETTE_COLORS[valueInRange];
+  },
+};
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/palettes/index.ts b/packages/kbn-coloring/src/shared_components/color_mapping/palettes/index.ts
new file mode 100644
index 0000000000000..340bbd32f0279
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/palettes/index.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright 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 { ColorMapping } from '../config';
+import { ElasticBrandPalette } from './elastic_brand';
+import { EUIAmsterdamColorBlindPalette } from './eui_amsterdam';
+import { KibanaV7LegacyPalette } from './kibana_legacy';
+import { NeutralPalette } from './neutral';
+
+export const AVAILABLE_PALETTES = new Map<string, ColorMapping.CategoricalPalette>([
+  [EUIAmsterdamColorBlindPalette.id, EUIAmsterdamColorBlindPalette],
+  [ElasticBrandPalette.id, ElasticBrandPalette],
+  [KibanaV7LegacyPalette.id, KibanaV7LegacyPalette],
+  [NeutralPalette.id, NeutralPalette],
+]);
+
+/**
+ * This function should be instanciated once at the root of the component with the available palettes and
+ * a choosed default one and shared across components to keep a single point of truth of the available palettes and the default
+ * one.
+ */
+export function getPalette(
+  palettes: Map<string, ColorMapping.CategoricalPalette>,
+  defaultPalette: ColorMapping.CategoricalPalette
+): (paletteId: string) => ColorMapping.CategoricalPalette {
+  return (paletteId) => palettes.get(paletteId) ?? defaultPalette;
+}
+
+export * from './eui_amsterdam';
+export * from './elastic_brand';
+export * from './kibana_legacy';
+export * from './neutral';
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/palettes/kibana_legacy.ts b/packages/kbn-coloring/src/shared_components/color_mapping/palettes/kibana_legacy.ts
new file mode 100644
index 0000000000000..9b576e0b05c66
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/palettes/kibana_legacy.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 { ColorMapping } from '../config';
+
+export const KIBANA_V7_LEGACY_PALETTE_COLORS = [
+  '#00a69b',
+  '#57c17b',
+  '#6f87d8',
+  '#663db8',
+  '#bc52bc',
+  '#9e3533',
+  '#daa05d',
+];
+
+export const KibanaV7LegacyPalette: ColorMapping.CategoricalPalette = {
+  id: 'kibana_v7_legacy',
+  name: 'Kibana Legacy',
+  colorCount: KIBANA_V7_LEGACY_PALETTE_COLORS.length,
+  type: 'categorical',
+  getColor(valueInRange) {
+    return KIBANA_V7_LEGACY_PALETTE_COLORS[valueInRange];
+  },
+};
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/palettes/neutral.ts b/packages/kbn-coloring/src/shared_components/color_mapping/palettes/neutral.ts
new file mode 100644
index 0000000000000..5d3d92790843b
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/palettes/neutral.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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 { ColorMapping } from '../config';
+
+const schemeGreys = ['#f2f4fb', '#d4d9e5', '#98a2b3', '#696f7d', '#353642'];
+export const NEUTRAL_COLOR_LIGHT = schemeGreys.slice();
+export const NEUTRAL_COLOR_DARK = schemeGreys.slice().reverse();
+
+export const NeutralPalette: ColorMapping.CategoricalPalette = {
+  id: 'neutral',
+  name: 'Neutral',
+  colorCount: NEUTRAL_COLOR_LIGHT.length,
+  type: 'categorical',
+  getColor(valueInRange, isDarkMode) {
+    return isDarkMode ? NEUTRAL_COLOR_DARK[valueInRange] : NEUTRAL_COLOR_LIGHT[valueInRange];
+  },
+};
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/state/color_mapping.ts b/packages/kbn-coloring/src/shared_components/color_mapping/state/color_mapping.ts
new file mode 100644
index 0000000000000..27588aff2b389
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/state/color_mapping.ts
@@ -0,0 +1,225 @@
+/*
+ * Copyright 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 { createSlice } from '@reduxjs/toolkit';
+import type { PayloadAction } from '@reduxjs/toolkit';
+import type { ColorMapping } from '../config';
+
+export interface RootState {
+  colorMapping: ColorMapping.Config;
+  ui: {
+    colorPicker: {
+      index: number;
+      visibile: boolean;
+      type: 'gradient' | 'assignment' | 'specialAssignment';
+    };
+  };
+}
+
+const initialState: RootState['colorMapping'] = {
+  assignmentMode: 'auto',
+  assignments: [],
+  specialAssignments: [],
+  paletteId: 'eui',
+  colorMode: { type: 'categorical' },
+};
+
+export const colorMappingSlice = createSlice({
+  name: 'colorMapping',
+  initialState,
+  reducers: {
+    updateModel: (state, action: PayloadAction<ColorMapping.Config>) => {
+      state.assignmentMode = action.payload.assignmentMode;
+      state.assignments = [...action.payload.assignments];
+      state.specialAssignments = [...action.payload.specialAssignments];
+      state.paletteId = action.payload.paletteId;
+      state.colorMode = { ...action.payload.colorMode };
+    },
+    updatePalette: (
+      state,
+      action: PayloadAction<{
+        assignments: ColorMapping.Config['assignments'];
+        paletteId: ColorMapping.Config['paletteId'];
+        colorMode: ColorMapping.Config['colorMode'];
+      }>
+    ) => {
+      state.paletteId = action.payload.paletteId;
+      state.assignments = [...action.payload.assignments];
+      state.colorMode = { ...action.payload.colorMode };
+    },
+    assignStatically: (state, action: PayloadAction<ColorMapping.Config['assignments']>) => {
+      state.assignmentMode = 'manual';
+      state.assignments = [...action.payload];
+    },
+    assignAutomatically: (state) => {
+      state.assignmentMode = 'auto';
+      state.assignments = [];
+    },
+
+    addNewAssignment: (
+      state,
+      action: PayloadAction<ColorMapping.Config['assignments'][number]>
+    ) => {
+      state.assignments.push({ ...action.payload });
+    },
+    updateAssignment: (
+      state,
+      action: PayloadAction<{
+        assignmentIndex: number;
+        assignment: ColorMapping.Config['assignments'][number];
+      }>
+    ) => {
+      state.assignments[action.payload.assignmentIndex] = {
+        ...action.payload.assignment,
+        touched: true,
+      };
+    },
+    updateAssignmentRule: (
+      state,
+      action: PayloadAction<{
+        assignmentIndex: number;
+        rule: ColorMapping.Config['assignments'][number]['rule'];
+      }>
+    ) => {
+      state.assignments[action.payload.assignmentIndex] = {
+        ...state.assignments[action.payload.assignmentIndex],
+        rule: action.payload.rule,
+      };
+    },
+    updateAssignmentColor: (
+      state,
+      action: PayloadAction<{
+        assignmentIndex: number;
+        color: ColorMapping.Config['assignments'][number]['color'];
+      }>
+    ) => {
+      state.assignments[action.payload.assignmentIndex] = {
+        ...state.assignments[action.payload.assignmentIndex],
+        color: action.payload.color,
+        touched: true,
+      };
+    },
+
+    updateSpecialAssignmentColor: (
+      state,
+      action: PayloadAction<{
+        assignmentIndex: number;
+        color: ColorMapping.Config['specialAssignments'][number]['color'];
+      }>
+    ) => {
+      state.specialAssignments[action.payload.assignmentIndex] = {
+        ...state.specialAssignments[action.payload.assignmentIndex],
+        color: action.payload.color,
+        touched: true,
+      };
+    },
+    removeAssignment: (state, action: PayloadAction<number>) => {
+      state.assignments.splice(action.payload, 1);
+    },
+    changeColorMode: (state, action: PayloadAction<ColorMapping.Config['colorMode']>) => {
+      state.colorMode = { ...action.payload };
+    },
+    updateGradientColorStep: (
+      state,
+      action: PayloadAction<{
+        index: number;
+        color: ColorMapping.CategoricalColor | ColorMapping.ColorCode;
+      }>
+    ) => {
+      if (state.colorMode.type !== 'gradient') {
+        return;
+      }
+
+      state.colorMode = {
+        ...state.colorMode,
+        steps: state.colorMode.steps.map((step, index) => {
+          return index === action.payload.index
+            ? { ...action.payload.color, touched: true }
+            : { ...step, touched: false };
+        }),
+      };
+    },
+    removeGradientColorStep: (state, action: PayloadAction<number>) => {
+      if (state.colorMode.type !== 'gradient') {
+        return;
+      }
+      const steps = [...state.colorMode.steps];
+      steps.splice(action.payload, 1);
+
+      // this maintain the correct sort direciton depending on which step
+      // gets removed from the array when only 2 steps are left.
+      const sort =
+        state.colorMode.steps.length === 2
+          ? state.colorMode.sort === 'desc'
+            ? action.payload === 0
+              ? 'asc'
+              : 'desc'
+            : action.payload === 0
+            ? 'desc'
+            : 'asc'
+          : state.colorMode.sort;
+
+      state.colorMode = {
+        ...state.colorMode,
+        steps: [...steps],
+        sort,
+      };
+    },
+    addGradientColorStep: (
+      state,
+      action: PayloadAction<{
+        color: ColorMapping.CategoricalColor | ColorMapping.ColorCode;
+        at: number;
+      }>
+    ) => {
+      if (state.colorMode.type !== 'gradient') {
+        return;
+      }
+
+      state.colorMode = {
+        ...state.colorMode,
+        steps: [
+          ...state.colorMode.steps.slice(0, action.payload.at),
+          { ...action.payload.color, touched: false },
+          ...state.colorMode.steps.slice(action.payload.at),
+        ],
+      };
+    },
+
+    changeGradientSortOrder: (state, action: PayloadAction<'asc' | 'desc'>) => {
+      if (state.colorMode.type !== 'gradient') {
+        return;
+      }
+
+      state.colorMode = {
+        ...state.colorMode,
+        sort: action.payload,
+      };
+    },
+  },
+});
+// Action creators are generated for each case reducer function
+export const {
+  updatePalette,
+  assignStatically,
+  assignAutomatically,
+  addNewAssignment,
+  updateAssignment,
+  updateAssignmentColor,
+  updateSpecialAssignmentColor,
+  updateAssignmentRule,
+  removeAssignment,
+  changeColorMode,
+  updateGradientColorStep,
+  removeGradientColorStep,
+  addGradientColorStep,
+  changeGradientSortOrder,
+  updateModel,
+} = colorMappingSlice.actions;
+
+export const colorMappingReducer = colorMappingSlice.reducer;
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/state/selectors.ts b/packages/kbn-coloring/src/shared_components/color_mapping/state/selectors.ts
new file mode 100644
index 0000000000000..69bd57d2d852e
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/state/selectors.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 { getPalette } from '../palettes';
+import { RootState } from './color_mapping';
+
+export function selectPalette(getPaletteFn: ReturnType<typeof getPalette>) {
+  return (state: RootState) => getPaletteFn(state.colorMapping.paletteId);
+}
+export function selectColorMode(state: RootState) {
+  return state.colorMapping.colorMode;
+}
+export function selectSpecialAssignments(state: RootState) {
+  return state.colorMapping.specialAssignments;
+}
+export function selectIsAutoAssignmentMode(state: RootState) {
+  return state.colorMapping.assignmentMode === 'auto';
+}
+export function selectColorPickerVisibility(state: RootState) {
+  return state.ui.colorPicker;
+}
diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/state/ui.ts b/packages/kbn-coloring/src/shared_components/color_mapping/state/ui.ts
new file mode 100644
index 0000000000000..632fb31e9dcc5
--- /dev/null
+++ b/packages/kbn-coloring/src/shared_components/color_mapping/state/ui.ts
@@ -0,0 +1,55 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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 PayloadAction, createSlice } from '@reduxjs/toolkit';
+import { RootState } from './color_mapping';
+
+const initialState: RootState['ui'] = {
+  colorPicker: {
+    index: 0,
+    visibile: false,
+    type: 'assignment',
+  },
+};
+
+export const uiSlice = createSlice({
+  name: 'colorMapping',
+  initialState,
+  reducers: {
+    colorPickerVisibility: (
+      state,
+      action: PayloadAction<{
+        index: number;
+        type: RootState['ui']['colorPicker']['type'];
+        visible: boolean;
+      }>
+    ) => {
+      state.colorPicker.visibile = action.payload.visible;
+      state.colorPicker.index = action.payload.index;
+      state.colorPicker.type = action.payload.type;
+    },
+    switchColorPickerVisibility: (state) => {
+      state.colorPicker.visibile = !state.colorPicker.visibile;
+    },
+    showColorPickerVisibility: (state) => {
+      state.colorPicker.visibile = true;
+    },
+    hideColorPickerVisibility: (state) => {
+      state.colorPicker.visibile = false;
+    },
+  },
+});
+
+export const {
+  colorPickerVisibility,
+  switchColorPickerVisibility,
+  showColorPickerVisibility,
+  hideColorPickerVisibility,
+} = uiSlice.actions;
+
+export const uiReducer = uiSlice.reducer;
diff --git a/packages/kbn-coloring/src/shared_components/index.ts b/packages/kbn-coloring/src/shared_components/index.ts
index 546224092e576..242df23b19e53 100644
--- a/packages/kbn-coloring/src/shared_components/index.ts
+++ b/packages/kbn-coloring/src/shared_components/index.ts
@@ -21,3 +21,5 @@ export const CustomizablePaletteLazy = React.lazy(() => import('./coloring'));
  * a predefined fallback and error boundary.
  */
 export const CustomizablePalette = withSuspense(CustomizablePaletteLazy);
+
+export * from './color_mapping';
diff --git a/packages/kbn-coloring/tsconfig.json b/packages/kbn-coloring/tsconfig.json
index 54c068f8bd3b6..315e59225601c 100644
--- a/packages/kbn-coloring/tsconfig.json
+++ b/packages/kbn-coloring/tsconfig.json
@@ -10,7 +10,6 @@
     ]
   },
   "include": [
-    "**/*.scss",
     "**/*.ts",
     "**/*.tsx"
   ],
@@ -21,6 +20,8 @@
     "@kbn/utility-types",
     "@kbn/shared-ux-utility",
     "@kbn/test-jest-helpers",
+    "@kbn/data-plugin",
+    "@kbn/ui-theme",
   ],
   "exclude": [
     "target/**/*",
diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml
index 78bba1df4220a..ec34d257eadc2 100644
--- a/packages/kbn-optimizer/limits.yml
+++ b/packages/kbn-optimizer/limits.yml
@@ -50,13 +50,13 @@ pageLoadAssetSize:
   expressionLegacyMetricVis: 23121
   expressionMetric: 22238
   expressionMetricVis: 23121
-  expressionPartitionVis: 26338
+  expressionPartitionVis: 28000
   expressionRepeatImage: 22341
   expressionRevealImage: 25675
   expressions: 140958
   expressionShape: 34008
   expressionTagcloud: 27505
-  expressionXY: 39500
+  expressionXY: 45000
   features: 21723
   fieldFormats: 65209
   files: 22673
diff --git a/src/plugins/chart_expressions/common/color_categories.ts b/src/plugins/chart_expressions/common/color_categories.ts
new file mode 100644
index 0000000000000..0bb8811f2701a
--- /dev/null
+++ b/src/plugins/chart_expressions/common/color_categories.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 { DatatableRow } from '@kbn/expressions-plugin/common';
+import { isMultiFieldKey } from '@kbn/data-plugin/common';
+
+/**
+ * Get the stringified version of all the categories that needs to be colored in the chart.
+ * Multifield keys will return as array of string and simple fields (numeric, string) will be returned as a plain unformatted string.
+ */
+export function getColorCategories(
+  rows: DatatableRow[],
+  accessor?: string
+): Array<string | string[]> {
+  return accessor
+    ? rows.reduce<{ keys: Set<string>; categories: Array<string | string[]> }>(
+        (acc, r) => {
+          const value = r[accessor];
+          if (value === undefined) {
+            return acc;
+          }
+          // The categories needs to be stringified in their unformatted version.
+          // We can't distinguish between a number and a string from a text input and the match should
+          // work with both numeric field values and string values.
+          const key = (isMultiFieldKey(value) ? [...value.keys] : [value]).map(String);
+          const stringifiedKeys = key.join(',');
+          if (!acc.keys.has(stringifiedKeys)) {
+            acc.keys.add(stringifiedKeys);
+            acc.categories.push(key.length === 1 ? key[0] : key);
+          }
+          return acc;
+        },
+        { keys: new Set(), categories: [] }
+      ).categories
+    : [];
+}
diff --git a/src/plugins/chart_expressions/common/index.ts b/src/plugins/chart_expressions/common/index.ts
index 0983b1ed28d4d..acc3b4d8c88cd 100644
--- a/src/plugins/chart_expressions/common/index.ts
+++ b/src/plugins/chart_expressions/common/index.ts
@@ -13,3 +13,4 @@ export {
   isOnAggBasedEditor,
 } from './utils';
 export type { Simplify, MakeOverridesSerializable } from './types';
+export { getColorCategories } from './color_categories';
diff --git a/src/plugins/chart_expressions/common/tsconfig.json b/src/plugins/chart_expressions/common/tsconfig.json
index f65660474561b..7ac76523fcb6c 100644
--- a/src/plugins/chart_expressions/common/tsconfig.json
+++ b/src/plugins/chart_expressions/common/tsconfig.json
@@ -15,5 +15,7 @@
   ],
   "kbn_references": [
     "@kbn/core-execution-context-common",
+    "@kbn/expressions-plugin",
+    "@kbn/data-plugin",
   ]
 }
diff --git a/src/plugins/chart_expressions/expression_legacy_metric/kibana.jsonc b/src/plugins/chart_expressions/expression_legacy_metric/kibana.jsonc
index 41a9c965a66da..a49ca80a2fcd2 100644
--- a/src/plugins/chart_expressions/expression_legacy_metric/kibana.jsonc
+++ b/src/plugins/chart_expressions/expression_legacy_metric/kibana.jsonc
@@ -12,7 +12,8 @@
       "fieldFormats",
       "charts",
       "visualizations",
-      "presentationUtil"
+      "presentationUtil",
+      "data"
     ],
     "optionalPlugins": [
       "usageCollection"
diff --git a/src/plugins/chart_expressions/expression_metric/kibana.jsonc b/src/plugins/chart_expressions/expression_metric/kibana.jsonc
index 087583e6fff6f..a53def7de36ee 100644
--- a/src/plugins/chart_expressions/expression_metric/kibana.jsonc
+++ b/src/plugins/chart_expressions/expression_metric/kibana.jsonc
@@ -12,7 +12,8 @@
       "fieldFormats",
       "charts",
       "visualizations",
-      "presentationUtil"
+      "presentationUtil",
+      "data"
     ],
     "optionalPlugins": [
       "usageCollection"
diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/mosaic_vis_function.test.ts.snap b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/mosaic_vis_function.test.ts.snap
index 604368d7ab130..a3cd4f976757b 100644
--- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/mosaic_vis_function.test.ts.snap
+++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/mosaic_vis_function.test.ts.snap
@@ -71,6 +71,7 @@ Object {
           "type": "vis_dimension",
         },
       ],
+      "colorMapping": undefined,
       "dimensions": Object {
         "buckets": Array [
           Object {
diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/pie_vis_function.test.ts.snap b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/pie_vis_function.test.ts.snap
index 293f86c6bf9ec..edcb2c8fd76e4 100644
--- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/pie_vis_function.test.ts.snap
+++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/pie_vis_function.test.ts.snap
@@ -61,6 +61,7 @@ Object {
           "type": "vis_dimension",
         },
       ],
+      "colorMapping": undefined,
       "dimensions": Object {
         "buckets": Array [
           Object {
@@ -203,6 +204,7 @@ Object {
           "type": "vis_dimension",
         },
       ],
+      "colorMapping": undefined,
       "dimensions": Object {
         "buckets": Array [
           Object {
diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/treemap_vis_function.test.ts.snap b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/treemap_vis_function.test.ts.snap
index f6817eca439cf..17c372547ad79 100644
--- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/treemap_vis_function.test.ts.snap
+++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/treemap_vis_function.test.ts.snap
@@ -71,6 +71,7 @@ Object {
           "type": "vis_dimension",
         },
       ],
+      "colorMapping": undefined,
       "dimensions": Object {
         "buckets": Array [
           Object {
diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/waffle_vis_function.test.ts.snap b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/waffle_vis_function.test.ts.snap
index 7c74291190a2d..cb1d724053dfe 100644
--- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/waffle_vis_function.test.ts.snap
+++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/waffle_vis_function.test.ts.snap
@@ -53,6 +53,7 @@ Object {
         },
         "type": "vis_dimension",
       },
+      "colorMapping": undefined,
       "dimensions": Object {
         "buckets": Array [
           Object {
diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/i18n.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/i18n.ts
index b312de7bf1583..c74669439b2c3 100644
--- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/i18n.ts
+++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/i18n.ts
@@ -122,6 +122,10 @@ export const strings = {
     i18n.translate('expressionPartitionVis.reusable.function.dimension.splitrow', {
       defaultMessage: 'Row split',
     }),
+  getColorMappingHelp: () =>
+    i18n.translate('expressionPartitionVis.layer.colorMapping.help', {
+      defaultMessage: 'JSON key-value pairs of the color mapping model',
+    }),
 };
 
 export const errors = {
diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/mosaic_vis_function.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/mosaic_vis_function.ts
index fc863cf73c68c..4a9dff714c8da 100644
--- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/mosaic_vis_function.ts
+++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/mosaic_vis_function.ts
@@ -110,6 +110,10 @@ export const mosaicVisFunction = (): MosaicVisExpressionFunctionDefinition => ({
       help: strings.getAriaLabelHelp(),
       required: false,
     },
+    colorMapping: {
+      types: ['string'],
+      help: strings.getColorMappingHelp(),
+    },
   },
   fn(context, args, handlers) {
     const maxSupportedBuckets = 2;
@@ -146,6 +150,7 @@ export const mosaicVisFunction = (): MosaicVisExpressionFunctionDefinition => ({
         splitColumn: args.splitColumn,
         splitRow: args.splitRow,
       },
+      colorMapping: args.colorMapping,
     };
 
     if (handlers?.inspectorAdapters?.tables) {
diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/pie_vis_function.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/pie_vis_function.ts
index 0cf6522456c62..30e8388f1255e 100644
--- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/pie_vis_function.ts
+++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/pie_vis_function.ts
@@ -141,6 +141,10 @@ export const pieVisFunction = (): PieVisExpressionFunctionDefinition => ({
       help: strings.getAriaLabelHelp(),
       required: false,
     },
+    colorMapping: {
+      types: ['string'],
+      help: strings.getColorMappingHelp(),
+    },
   },
   fn(context, args, handlers) {
     if (args.splitColumn && args.splitRow) {
@@ -173,6 +177,7 @@ export const pieVisFunction = (): PieVisExpressionFunctionDefinition => ({
         splitColumn: args.splitColumn,
         splitRow: args.splitRow,
       },
+      colorMapping: args.colorMapping,
     };
 
     if (handlers?.inspectorAdapters?.tables) {
diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/treemap_vis_function.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/treemap_vis_function.ts
index 2a5d0a6af7a8a..e0804dd9b0e92 100644
--- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/treemap_vis_function.ts
+++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/treemap_vis_function.ts
@@ -115,6 +115,10 @@ export const treemapVisFunction = (): TreemapVisExpressionFunctionDefinition =>
       help: strings.getAriaLabelHelp(),
       required: false,
     },
+    colorMapping: {
+      types: ['string'],
+      help: strings.getColorMappingHelp(),
+    },
   },
   fn(context, args, handlers) {
     const maxSupportedBuckets = 2;
@@ -152,6 +156,7 @@ export const treemapVisFunction = (): TreemapVisExpressionFunctionDefinition =>
         splitColumn: args.splitColumn,
         splitRow: args.splitRow,
       },
+      colorMapping: args.colorMapping,
     };
 
     if (handlers?.inspectorAdapters?.tables) {
diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/waffle_vis_function.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/waffle_vis_function.ts
index e4176cf6015c1..6e23513851b1e 100644
--- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/waffle_vis_function.ts
+++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/waffle_vis_function.ts
@@ -114,6 +114,10 @@ export const waffleVisFunction = (): WaffleVisExpressionFunctionDefinition => ({
       help: strings.getAriaLabelHelp(),
       required: false,
     },
+    colorMapping: {
+      types: ['string'],
+      help: strings.getColorMappingHelp(),
+    },
   },
   fn(context, args, handlers) {
     if (args.splitColumn && args.splitRow) {
@@ -147,6 +151,7 @@ export const waffleVisFunction = (): WaffleVisExpressionFunctionDefinition => ({
         splitColumn: args.splitColumn,
         splitRow: args.splitRow,
       },
+      colorMapping: args.colorMapping,
     };
 
     if (handlers?.inspectorAdapters?.tables) {
diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_renderers.ts b/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_renderers.ts
index 239253f54491d..00667ace39576 100644
--- a/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_renderers.ts
+++ b/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_renderers.ts
@@ -61,6 +61,7 @@ interface VisCommonParams {
   maxLegendLines: number;
   legendSize?: LegendSize;
   ariaLabel?: string;
+  colorMapping?: string; // JSON stringified object of the color mapping
 }
 
 interface VisCommonConfig extends VisCommonParams {
diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/colors/color_mapping_accessors.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/colors/color_mapping_accessors.ts
new file mode 100644
index 0000000000000..ec11fc7605de1
--- /dev/null
+++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/colors/color_mapping_accessors.ts
@@ -0,0 +1,77 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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 { NodeColorAccessor, PATH_KEY } from '@elastic/charts';
+import { lightenColor } from '@kbn/charts-plugin/public';
+import { MultiFieldKey } from '@kbn/data-plugin/common';
+import { getColorFactory } from '@kbn/coloring';
+import { isMultiFieldKey } from '@kbn/data-plugin/common';
+import { ChartTypes } from '../../../common/types';
+
+export function getCategoryKeys(category: string | MultiFieldKey): string | string[] {
+  return isMultiFieldKey(category) ? category.keys.map(String) : `${category}`;
+}
+
+/**
+ * Get the color of a specific slice/section in Pie,donut,waffle and treemap.
+ * These chart type shares the same color assignment mechanism.
+ */
+const getPieFillColor =
+  (
+    layerIndex: number,
+    numOfLayers: number,
+    getColorFn: ReturnType<typeof getColorFactory>
+  ): NodeColorAccessor =>
+  (_key, _sortIndex, node) => {
+    const path = node[PATH_KEY];
+    // the category used to color the pie/donut is at the third level of the path
+    // first two are: small multiple and pie whole center.
+    const category = getCategoryKeys(path[2].value);
+    const color = getColorFn(category);
+    // increase the lightness of the color on each layer.
+    return lightenColor(color, layerIndex + 1, numOfLayers);
+  };
+
+/**
+ * Get the color of a section in a Mosaic chart.
+ * This chart has a slight variation in the way color are applied. Mosaic can represent up to 2 layers,
+ * described in lens as the horizontal and vertical axes.
+ * With a single layer the color is simply applied per each category, with 2 layer, the color is applied only
+ * to the category that describe a row, not by column.
+ */
+const getMosaicFillColor =
+  (
+    layerIndex: number,
+    numOfLayers: number,
+    getColorFn: ReturnType<typeof getColorFactory>
+  ): NodeColorAccessor =>
+  (_key, _sortIndex, node) => {
+    // Special case for 2 layer mosaic where the color is per rows and the columns are not colored
+    if (numOfLayers === 2 && layerIndex === 0) {
+      // transparent color will fallback to the kibana/context background
+      return 'rgba(0,0,0,0)';
+    }
+    const path = node[PATH_KEY];
+
+    // the category used to color the pie/donut is at the third level of the `path` when using a single layer mosaic
+    // and are at fourth level of `path` when using 2 layer mosaic
+    // first two are: small multiple and pie whole center.
+    const category = getCategoryKeys(numOfLayers === 2 ? path[3].value : path[2].value);
+    return getColorFn(category);
+  };
+
+export const getPartitionFillColor = (
+  chartType: ChartTypes,
+  layerIndex: number,
+  numOfLayers: number,
+  getColorFn: ReturnType<typeof getColorFactory>
+): NodeColorAccessor => {
+  return chartType === ChartTypes.MOSAIC
+    ? getMosaicFillColor(layerIndex, numOfLayers, getColorFn)
+    : getPieFillColor(layerIndex, numOfLayers, getColorFn);
+};
diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.ts
index e4f3e1687e4ad..6f40097809e18 100644
--- a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.ts
+++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.ts
@@ -7,16 +7,25 @@
  */
 
 import { Datum, PartitionLayer } from '@elastic/charts';
-import type { PaletteRegistry } from '@kbn/coloring';
+import {
+  PaletteRegistry,
+  getColorFactory,
+  getPalette,
+  AVAILABLE_PALETTES,
+  NeutralPalette,
+} from '@kbn/coloring';
 import { i18n } from '@kbn/i18n';
 import { FieldFormat } from '@kbn/field-formats-plugin/common';
 import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
 import type { Datatable, DatatableRow } from '@kbn/expressions-plugin/public';
+
+import { getColorCategories } from '@kbn/chart-expressions-common';
 import { getDistinctSeries } from '..';
 import { BucketColumns, ChartTypes, PartitionVisParams } from '../../../common/types';
 import { sortPredicateByType, sortPredicateSaveSourceOrder } from './sort_predicate';
 import { byDataColorPaletteMap, getColor } from './get_color';
 import { getNodeLabel } from './get_node_labels';
+import { getPartitionFillColor } from '../colors/color_mapping_accessors';
 
 // This is particularly useful in case of a text based languages where
 // it's no possible to use a missingBucketLabel
@@ -62,6 +71,15 @@ export const getLayers = (
 
   const distinctSeries = getDistinctSeries(rows, columns);
 
+  // return a fn only if color mapping is available in visParams
+  const getColorFromMappingFn = getColorFromMappingFactory(
+    chartType,
+    columns,
+    rows,
+    isDarkMode,
+    visParams
+  );
+
   return columns.map((col, layerIndex) => {
     return {
       groupByRollup: (d: Datum) => (col.id ? d[col.id] ?? emptySliceLabel : col.name),
@@ -75,26 +93,74 @@ export const getLayers = (
         ? sortPredicateSaveSourceOrder()
         : sortPredicateForType,
       shape: {
-        fillColor: (key, sortIndex, node) =>
-          getColor(
-            chartType,
-            key,
-            node,
-            layerIndex,
-            isSplitChart,
-            overwriteColors,
-            distinctSeries,
-            { columnsLength: columns.length, rowsLength: rows.length },
-            visParams,
-            palettes,
-            byDataPalette,
-            syncColors,
-            isDarkMode,
-            formatter,
-            col,
-            formatters
-          ),
+        // this applies color mapping only if visParams.colorMapping is available
+        fillColor: getColorFromMappingFn
+          ? getPartitionFillColor(chartType, layerIndex, columns.length, getColorFromMappingFn)
+          : (key, sortIndex, node) =>
+              getColor(
+                chartType,
+                key,
+                node,
+                layerIndex,
+                isSplitChart,
+                overwriteColors,
+                distinctSeries,
+                { columnsLength: columns.length, rowsLength: rows.length },
+                visParams,
+                palettes,
+                byDataPalette,
+                syncColors,
+                isDarkMode,
+                formatter,
+                col,
+                formatters
+              ),
       },
     };
   });
 };
+
+/**
+ * If colorMapping is available, returns a function that accept a string or an array of strings (used in case of multi-field-key)
+ * and returns a color specified in the provided mapping
+ */
+function getColorFromMappingFactory(
+  chartType: ChartTypes,
+  columns: Array<Partial<BucketColumns>>,
+  rows: DatatableRow[],
+  isDarkMode: boolean,
+  visParams: PartitionVisParams
+): undefined | ((category: string | string[]) => string) {
+  const { colorMapping, dimensions } = visParams;
+
+  if (!colorMapping) {
+    // return undefined, we will use the legacy color mapping instead
+    return undefined;
+  }
+  // if pie/donut/treemap with no buckets use the default color mode
+  if (
+    (chartType === ChartTypes.DONUT ||
+      chartType === ChartTypes.PIE ||
+      chartType === ChartTypes.TREEMAP) &&
+    (!dimensions.buckets || dimensions.buckets?.length === 0)
+  ) {
+    return undefined;
+  }
+  // the mosaic configures the main categories in the second column, instead of the first
+  // as it happens in all the other partition types.
+  // Independentely from the bucket aggregation used, the categories will always be casted
+  // as string to make it nicely working with a text input field, avoiding a field
+  const categories =
+    chartType === ChartTypes.MOSAIC && columns.length === 2
+      ? getColorCategories(rows, columns[1]?.id)
+      : getColorCategories(rows, columns[0]?.id);
+  return getColorFactory(
+    JSON.parse(colorMapping),
+    getPalette(AVAILABLE_PALETTES, NeutralPalette),
+    isDarkMode,
+    {
+      type: 'categories',
+      categories,
+    }
+  );
+}
diff --git a/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/__snapshots__/tagcloud_function.test.ts.snap b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/__snapshots__/tagcloud_function.test.ts.snap
index d03cebd680290..15f335df82684 100644
--- a/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/__snapshots__/tagcloud_function.test.ts.snap
+++ b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/__snapshots__/tagcloud_function.test.ts.snap
@@ -66,6 +66,7 @@ Object {
       "bucket": Object {
         "accessor": 1,
       },
+      "colorMapping": undefined,
       "isPreview": false,
       "maxFontSize": 72,
       "metric": Object {
@@ -126,6 +127,7 @@ Object {
         },
         "type": "vis_dimension",
       },
+      "colorMapping": undefined,
       "isPreview": false,
       "maxFontSize": 72,
       "metric": Object {
diff --git a/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.ts b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.ts
index ec69431cd1735..75148e570331c 100644
--- a/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.ts
+++ b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.ts
@@ -51,6 +51,9 @@ const strings = {
     isPreview: i18n.translate('expressionTagcloud.functions.tagcloud.args.isPreviewHelpText', {
       defaultMessage: 'Set isPreview to true to avoid showing out of room warnings',
     }),
+    colorMapping: i18n.translate('expressionTagcloud.layer.colorMapping.help', {
+      defaultMessage: 'JSON key-value pairs of the color mapping model',
+    }),
   },
   dimension: {
     tags: i18n.translate('expressionTagcloud.functions.tagcloud.dimension.tags', {
@@ -146,6 +149,10 @@ export const tagcloudFunction: ExpressionTagcloudFunction = () => {
         default: false,
         required: false,
       },
+      colorMapping: {
+        types: ['string'],
+        help: argHelp.colorMapping,
+      },
     },
     fn(input, args, handlers) {
       validateAccessor(args.metric, input.columns);
@@ -167,6 +174,7 @@ export const tagcloudFunction: ExpressionTagcloudFunction = () => {
           (handlers.variables?.embeddableTitle as string) ??
           handlers.getExecutionContext?.()?.description,
         isPreview: Boolean(args.isPreview),
+        colorMapping: args.colorMapping,
       };
 
       if (handlers?.inspectorAdapters?.tables) {
diff --git a/src/plugins/chart_expressions/expression_tagcloud/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_tagcloud/common/types/expression_functions.ts
index 985da788c6ffc..c59e70a5c028d 100644
--- a/src/plugins/chart_expressions/expression_tagcloud/common/types/expression_functions.ts
+++ b/src/plugins/chart_expressions/expression_tagcloud/common/types/expression_functions.ts
@@ -27,6 +27,7 @@ interface TagCloudCommonParams {
   metric: ExpressionValueVisDimension | string;
   bucket?: ExpressionValueVisDimension | string;
   palette: PaletteOutput;
+  colorMapping?: string; // JSON stringified object of the color mapping
 }
 
 export interface TagCloudVisConfig extends TagCloudCommonParams {
diff --git a/src/plugins/chart_expressions/expression_tagcloud/kibana.jsonc b/src/plugins/chart_expressions/expression_tagcloud/kibana.jsonc
index 6c6ce82d321ed..b6bf410e2786f 100644
--- a/src/plugins/chart_expressions/expression_tagcloud/kibana.jsonc
+++ b/src/plugins/chart_expressions/expression_tagcloud/kibana.jsonc
@@ -8,6 +8,7 @@
     "server": true,
     "browser": true,
     "requiredPlugins": [
+      "data",
       "expressions",
       "visualizations",
       "charts",
diff --git a/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.test.tsx b/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.test.tsx
index 3f9c86778e82d..86c4bc009d931 100644
--- a/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.test.tsx
+++ b/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.test.tsx
@@ -105,6 +105,7 @@ describe('TagCloudChart', function () {
       renderComplete: jest.fn(),
       syncColors: false,
       visType: 'tagcloud',
+      isDarkMode: false,
     };
 
     wrapperPropsWithColumnNames = {
@@ -135,6 +136,7 @@ describe('TagCloudChart', function () {
       renderComplete: jest.fn(),
       syncColors: false,
       visType: 'tagcloud',
+      isDarkMode: false,
     };
   });
 
diff --git a/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.tsx b/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.tsx
index adfc3df81f97f..e3532bb17f97e 100644
--- a/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.tsx
+++ b/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.tsx
@@ -13,11 +13,19 @@ import { EuiIconTip, EuiResizeObserver } from '@elastic/eui';
 import { IconChartTagcloud } from '@kbn/chart-icons';
 import { Chart, Settings, Wordcloud, RenderChangeListener } from '@elastic/charts';
 import { EmptyPlaceholder } from '@kbn/charts-plugin/public';
-import type { PaletteRegistry, PaletteOutput } from '@kbn/coloring';
-import { IInterpreterRenderHandlers } from '@kbn/expressions-plugin/public';
-import { getOverridesFor } from '@kbn/chart-expressions-common';
+import {
+  PaletteRegistry,
+  PaletteOutput,
+  getColorFactory,
+  getPalette,
+  AVAILABLE_PALETTES,
+  NeutralPalette,
+} from '@kbn/coloring';
+import { IInterpreterRenderHandlers, DatatableRow } from '@kbn/expressions-plugin/public';
+import { getColorCategories, getOverridesFor } from '@kbn/chart-expressions-common';
 import type { AllowedSettingsOverrides, AllowedChartOverrides } from '@kbn/charts-plugin/common';
 import { getColumnByAccessor, getFormatByAccessor } from '@kbn/visualizations-plugin/common/utils';
+import { isMultiFieldKey } from '@kbn/data-plugin/common';
 import { getFormatService } from '../format_service';
 import { TagcloudRendererConfig } from '../../common/types';
 import { ScaleOptions, Orientation } from '../../common/constants';
@@ -31,6 +39,7 @@ export type TagCloudChartProps = TagcloudRendererConfig & {
   renderComplete: IInterpreterRenderHandlers['done'];
   palettesRegistry: PaletteRegistry;
   overrides?: AllowedSettingsOverrides & AllowedChartOverrides;
+  isDarkMode: boolean;
 };
 
 const calculateWeight = (value: number, x1: number, y1: number, x2: number, y2: number) =>
@@ -84,9 +93,10 @@ export const TagCloudChart = ({
   renderComplete,
   syncColors,
   overrides,
+  isDarkMode,
 }: TagCloudChartProps) => {
   const [warning, setWarning] = useState(false);
-  const { bucket, metric, scale, palette, showLabel, orientation } = visParams;
+  const { bucket, metric, scale, palette, showLabel, orientation, colorMapping } = visParams;
 
   const bucketFormatter = useMemo(() => {
     return bucket
@@ -96,23 +106,35 @@ export const TagCloudChart = ({
 
   const tagCloudData = useMemo(() => {
     const bucketColumn = bucket ? getColumnByAccessor(bucket, visData.columns)! : null;
-    const tagColumn = bucket ? bucketColumn!.id : null;
+    const tagColumn = bucket ? bucketColumn!.id : undefined;
     const metricColumn = getColumnByAccessor(metric, visData.columns)!.id;
 
     const metrics = visData.rows.map((row) => row[metricColumn]);
-    const values = bucket && tagColumn !== null ? visData.rows.map((row) => row[tagColumn]) : [];
+    const values =
+      bucket && tagColumn !== undefined ? visData.rows.map((row) => row[tagColumn]) : [];
     const maxValue = Math.max(...metrics);
     const minValue = Math.min(...metrics);
 
+    const colorFromMappingFn = getColorFromMappingFactory(
+      tagColumn,
+      visData.rows,
+      isDarkMode,
+      colorMapping
+    );
+
     return visData.rows.map((row) => {
-      const tag = tagColumn === null ? 'all' : row[tagColumn];
+      const tag = tagColumn === undefined ? 'all' : row[tagColumn];
+
+      const category = isMultiFieldKey(tag) ? tag.keys.map(String) : `${tag}`;
       return {
         text: bucketFormatter ? bucketFormatter.convert(tag, 'text') : tag,
         weight:
           tag === 'all' || visData.rows.length <= 1
             ? 1
             : calculateWeight(row[metricColumn], minValue, maxValue, 0, 1) || 0,
-        color: getColor(palettesRegistry, palette, tag, values, syncColors) || 'rgba(0,0,0,0)',
+        color: colorFromMappingFn
+          ? colorFromMappingFn(category)
+          : getColor(palettesRegistry, palette, tag, values, syncColors) || 'rgba(0,0,0,0)',
       };
     });
   }, [
@@ -124,6 +146,8 @@ export const TagCloudChart = ({
     syncColors,
     visData.columns,
     visData.rows,
+    colorMapping,
+    isDarkMode,
   ]);
 
   useEffect(() => {
@@ -278,3 +302,28 @@ export const TagCloudChart = ({
 
 // eslint-disable-next-line import/no-default-export
 export { TagCloudChart as default };
+
+/**
+ * If colorMapping is available, returns a function that accept a string or an array of strings (used in case of multi-field-key)
+ * and returns a color specified in the provided mapping
+ */
+function getColorFromMappingFactory(
+  tagColumn: string | undefined,
+  rows: DatatableRow[],
+  isDarkMode: boolean,
+  colorMapping?: string
+): undefined | ((category: string | string[]) => string) {
+  if (!colorMapping) {
+    // return undefined, we will use the legacy color mapping instead
+    return undefined;
+  }
+  return getColorFactory(
+    JSON.parse(colorMapping),
+    getPalette(AVAILABLE_PALETTES, NeutralPalette),
+    isDarkMode,
+    {
+      type: 'categories',
+      categories: getColorCategories(rows, tagColumn),
+    }
+  );
+}
diff --git a/src/plugins/chart_expressions/expression_tagcloud/public/expression_renderers/tagcloud_renderer.tsx b/src/plugins/chart_expressions/expression_tagcloud/public/expression_renderers/tagcloud_renderer.tsx
index b3ab496447754..101c40b6b384d 100644
--- a/src/plugins/chart_expressions/expression_tagcloud/public/expression_renderers/tagcloud_renderer.tsx
+++ b/src/plugins/chart_expressions/expression_tagcloud/public/expression_renderers/tagcloud_renderer.tsx
@@ -67,6 +67,12 @@ export const tagcloudRenderer: (
     };
 
     const palettesRegistry = await plugins.charts.palettes.getPalettes();
+    let isDarkMode = false;
+    plugins.charts.theme.darkModeEnabled$
+      .subscribe((val) => {
+        isDarkMode = val.darkMode;
+      })
+      .unsubscribe();
 
     render(
       <KibanaThemeProvider theme$={core.theme.theme$}>
@@ -87,6 +93,7 @@ export const tagcloudRenderer: (
                   fireEvent={handlers.event}
                   syncColors={config.syncColors}
                   overrides={config.overrides}
+                  isDarkMode={isDarkMode}
                 />
               </VisualizationContainer>
             )}
diff --git a/src/plugins/chart_expressions/expression_tagcloud/tsconfig.json b/src/plugins/chart_expressions/expression_tagcloud/tsconfig.json
index 55e81302586b8..b737dfb445f09 100644
--- a/src/plugins/chart_expressions/expression_tagcloud/tsconfig.json
+++ b/src/plugins/chart_expressions/expression_tagcloud/tsconfig.json
@@ -27,6 +27,7 @@
     "@kbn/analytics",
     "@kbn/chart-expressions-common",
     "@kbn/chart-icons",
+    "@kbn/data-plugin",
   ],
   "exclude": [
     "target/**/*",
diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/common_data_layer_args.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/common_data_layer_args.ts
index 10f6d5d748b23..b9e2bd6dbac67 100644
--- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/common_data_layer_args.ts
+++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/common_data_layer_args.ts
@@ -94,4 +94,8 @@ export const commonDataLayerArgs: Omit<
     help: strings.getPaletteHelp(),
     default: '{palette}',
   },
+  colorMapping: {
+    types: ['string'],
+    help: strings.getColorMappingHelp(),
+  },
 };
diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts
index 94d788106acb3..03df575b3c653 100644
--- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts
+++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts
@@ -52,6 +52,7 @@ const createDataLayer = (args: XYArgs, table: Datatable): DataLayerConfigResult
     layerType: LayerTypes.DATA,
     table: normalizedTable,
     showLines: args.showLines,
+    colorMapping: args.colorMapping,
     ...accessors,
   };
 };
diff --git a/src/plugins/chart_expressions/expression_xy/common/i18n/index.tsx b/src/plugins/chart_expressions/expression_xy/common/i18n/index.tsx
index d9fc015c2844c..2446a27e718ce 100644
--- a/src/plugins/chart_expressions/expression_xy/common/i18n/index.tsx
+++ b/src/plugins/chart_expressions/expression_xy/common/i18n/index.tsx
@@ -209,6 +209,10 @@ export const strings = {
     i18n.translate('expressionXY.dataLayer.palette.help', {
       defaultMessage: 'Palette',
     }),
+  getColorMappingHelp: () =>
+    i18n.translate('expressionXY.layer.colorMapping.help', {
+      defaultMessage: 'JSON key-value pairs of the color mapping model',
+    }),
   getTableHelp: () =>
     i18n.translate('expressionXY.layers.table.help', {
       defaultMessage: 'Table',
diff --git a/src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts
index 55fd63786570b..a81128f6e74a7 100644
--- a/src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts
+++ b/src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts
@@ -136,6 +136,7 @@ export interface DataLayerArgs {
   isStacked: boolean;
   isHorizontal: boolean;
   palette: PaletteOutput;
+  colorMapping?: string; // JSON stringified object of the color mapping
   decorations?: DataDecorationConfigResult[];
   curveType?: XYCurveType;
 }
@@ -163,6 +164,7 @@ export interface ExtendedDataLayerArgs {
   isStacked: boolean;
   isHorizontal: boolean;
   palette: PaletteOutput;
+  colorMapping?: string;
   // palette will always be set on the expression
   decorations?: DataDecorationConfigResult[];
   curveType?: XYCurveType;
diff --git a/src/plugins/chart_expressions/expression_xy/public/components/__snapshots__/xy_chart.test.tsx.snap b/src/plugins/chart_expressions/expression_xy/public/components/__snapshots__/xy_chart.test.tsx.snap
index fe76259b65889..9bc59b677ed78 100644
--- a/src/plugins/chart_expressions/expression_xy/public/components/__snapshots__/xy_chart.test.tsx.snap
+++ b/src/plugins/chart_expressions/expression_xy/public/components/__snapshots__/xy_chart.test.tsx.snap
@@ -1099,6 +1099,7 @@ exports[`XYChart component it renders area 1`] = `
             },
           }
         }
+        isDarkMode={false}
         layers={
           Array [
             Object {
@@ -2107,6 +2108,7 @@ exports[`XYChart component it renders bar 1`] = `
             },
           }
         }
+        isDarkMode={false}
         layers={
           Array [
             Object {
@@ -3115,6 +3117,7 @@ exports[`XYChart component it renders horizontal bar 1`] = `
             },
           }
         }
+        isDarkMode={false}
         layers={
           Array [
             Object {
@@ -4123,6 +4126,7 @@ exports[`XYChart component it renders line 1`] = `
             },
           }
         }
+        isDarkMode={false}
         layers={
           Array [
             Object {
@@ -5131,6 +5135,7 @@ exports[`XYChart component it renders stacked area 1`] = `
             },
           }
         }
+        isDarkMode={false}
         layers={
           Array [
             Object {
@@ -6139,6 +6144,7 @@ exports[`XYChart component it renders stacked bar 1`] = `
             },
           }
         }
+        isDarkMode={false}
         layers={
           Array [
             Object {
@@ -7147,6 +7153,7 @@ exports[`XYChart component it renders stacked horizontal bar 1`] = `
             },
           }
         }
+        isDarkMode={false}
         layers={
           Array [
             Object {
@@ -8381,6 +8388,7 @@ exports[`XYChart component split chart should render split chart if both, splitR
             },
           }
         }
+        isDarkMode={false}
         layers={
           Array [
             Object {
@@ -9622,6 +9630,7 @@ exports[`XYChart component split chart should render split chart if splitColumnA
             },
           }
         }
+        isDarkMode={false}
         layers={
           Array [
             Object {
@@ -10861,6 +10870,7 @@ exports[`XYChart component split chart should render split chart if splitRowAcce
             },
           }
         }
+        isDarkMode={false}
         layers={
           Array [
             Object {
diff --git a/src/plugins/chart_expressions/expression_xy/public/components/data_layers.tsx b/src/plugins/chart_expressions/expression_xy/public/components/data_layers.tsx
index 5cabeaee31575..cc6e969a10af9 100644
--- a/src/plugins/chart_expressions/expression_xy/public/components/data_layers.tsx
+++ b/src/plugins/chart_expressions/expression_xy/public/components/data_layers.tsx
@@ -57,6 +57,7 @@ interface Props {
   fieldFormats: LayersFieldFormats;
   uiState?: PersistedState;
   singleTable?: boolean;
+  isDarkMode: boolean;
 }
 
 export const DataLayers: FC<Props> = ({
@@ -80,6 +81,7 @@ export const DataLayers: FC<Props> = ({
   fieldFormats,
   uiState,
   singleTable,
+  isDarkMode,
 }) => {
   // for singleTable mode we should use y accessors from all layers for creating correct series name and getting color
   const allYAccessors = layers.flatMap((layer) => layer.accessors);
@@ -169,6 +171,7 @@ export const DataLayers: FC<Props> = ({
             allYAccessors,
             singleTable,
             multipleLayersWithSplits,
+            isDarkMode,
           });
 
           const index = `${layer.layerId}-${accessorIndex}`;
diff --git a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx
index b8ac9d5cd0bbb..c241e476db5de 100644
--- a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx
+++ b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx
@@ -964,6 +964,7 @@ export function XYChart({
               fieldFormats={fieldFormats}
               uiState={uiState}
               singleTable={singleTable}
+              isDarkMode={darkMode}
             />
           )}
           {referenceLineLayers.length ? (
diff --git a/src/plugins/chart_expressions/expression_xy/public/helpers/color/color_mapping_accessor.ts b/src/plugins/chart_expressions/expression_xy/public/helpers/color/color_mapping_accessor.ts
new file mode 100644
index 0000000000000..b57f371eab2fd
--- /dev/null
+++ b/src/plugins/chart_expressions/expression_xy/public/helpers/color/color_mapping_accessor.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 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 { SeriesColorAccessorFn } from '@elastic/charts';
+import { getColorFactory, type ColorMapping, type ColorMappingInputData } from '@kbn/coloring';
+import { MULTI_FIELD_KEY_SEPARATOR } from '@kbn/data-plugin/common';
+
+/**
+ * Return a color accessor function for XY charts depending on the split accessors received.
+ */
+export function getColorSeriesAccessorFn(
+  config: ColorMapping.Config,
+  getPaletteFn: (paletteId: string) => ColorMapping.CategoricalPalette,
+  isDarkMode: boolean,
+  mappingData: ColorMappingInputData,
+  fieldId: string,
+  specialTokens: Map<string, string>
+): SeriesColorAccessorFn {
+  // inverse map to handle the conversion between the formatted string and their original format
+  // for any specified special tokens
+  const specialHandlingInverseMap: Map<string, string> = new Map(
+    [...specialTokens.entries()].map((d) => [d[1], d[0]])
+  );
+
+  const getColor = getColorFactory(config, getPaletteFn, isDarkMode, mappingData);
+
+  return ({ splitAccessors }) => {
+    const splitValue = splitAccessors.get(fieldId);
+    // if there isn't a category associated in the split accessor, let's use the default color
+    if (splitValue === undefined) {
+      return null;
+    }
+
+    // category can be also a number, range, ip, multi-field. We need to stringify it to be sure
+    // we can correctly match it a with user string
+    // if the separator exist, we de-construct it into a multifieldkey into values.
+    const categories = `${splitValue}`.split(MULTI_FIELD_KEY_SEPARATOR).map((category) => {
+      return specialHandlingInverseMap.get(category) ?? category;
+    });
+    // we must keep the array nature of a multi-field key or just use a single string
+    // This is required because the rule stored are checked differently for single values or multi-values
+    return getColor(categories.length > 1 ? categories : categories[0]);
+  };
+}
diff --git a/src/plugins/chart_expressions/expression_xy/public/helpers/color_assignment.ts b/src/plugins/chart_expressions/expression_xy/public/helpers/color_assignment.ts
index 94b187055e6dd..990d1ab93a1bc 100644
--- a/src/plugins/chart_expressions/expression_xy/public/helpers/color_assignment.ts
+++ b/src/plugins/chart_expressions/expression_xy/public/helpers/color_assignment.ts
@@ -95,6 +95,11 @@ export const getAllSeries = (
   return allSeries;
 };
 
+/**
+ * This function joins every data series name available on each layer by the same color palette.
+ * The returned function `getRank` should return the position of a series name in this unified list by palette.
+ *
+ */
 export function getColorAssignments(
   layers: CommonXYLayerConfig[],
   titles: LayersAccessorsTitles,
diff --git a/src/plugins/chart_expressions/expression_xy/public/helpers/data_layers.tsx b/src/plugins/chart_expressions/expression_xy/public/helpers/data_layers.tsx
index ff76ec511ffc9..1971409ab4223 100644
--- a/src/plugins/chart_expressions/expression_xy/public/helpers/data_layers.tsx
+++ b/src/plugins/chart_expressions/expression_xy/public/helpers/data_layers.tsx
@@ -16,6 +16,7 @@ import {
   SeriesName,
   StackMode,
   XYChartSeriesIdentifier,
+  SeriesColorAccessorFn,
 } from '@elastic/charts';
 import { IFieldFormat } from '@kbn/field-formats-plugin/common';
 import type { PersistedState } from '@kbn/visualizations-plugin/public';
@@ -23,6 +24,13 @@ import { Datatable } from '@kbn/expressions-plugin/common';
 import { getAccessorByDimension } from '@kbn/visualizations-plugin/common/utils';
 import type { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common/expression_functions';
 import { PaletteRegistry, SeriesLayer } from '@kbn/coloring';
+import {
+  getPalette,
+  AVAILABLE_PALETTES,
+  NeutralPalette,
+  SPECIAL_TOKENS_STRING_CONVERTION,
+} from '@kbn/coloring';
+import { getColorCategories } from '@kbn/chart-expressions-common';
 import { isDataLayer } from '../../common/utils/layer_types_guards';
 import { CommonXYDataLayerConfig, CommonXYLayerConfig, XScaleType } from '../../common';
 import { AxisModes, SeriesTypes } from '../../common/constants';
@@ -32,6 +40,7 @@ import { ColorAssignments } from './color_assignment';
 import { GroupsConfiguration } from './axes_configuration';
 import { LayerAccessorsTitles, LayerFieldFormats, LayersFieldFormats } from './layers';
 import { getFormat } from './format';
+import { getColorSeriesAccessorFn } from './color/color_mapping_accessor';
 
 type SeriesSpec = LineSeriesProps & BarSeriesProps & AreaSeriesProps;
 
@@ -57,6 +66,7 @@ type GetSeriesPropsFn = (config: {
   allYAccessors: Array<string | ExpressionValueVisDimension>;
   singleTable?: boolean;
   multipleLayersWithSplits: boolean;
+  isDarkMode: boolean;
 }) => SeriesSpec;
 
 type GetSeriesNameFn = (
@@ -399,6 +409,7 @@ export const getSeriesProps: GetSeriesPropsFn = ({
   allYAccessors,
   singleTable,
   multipleLayersWithSplits,
+  isDarkMode,
 }): SeriesSpec => {
   const { table, isStacked, markSizeAccessor } = layer;
   const isPercentage = layer.isPercentage;
@@ -478,6 +489,34 @@ export const getSeriesProps: GetSeriesPropsFn = ({
     );
   };
 
+  const colorAccessorFn: SeriesColorAccessorFn =
+    // if colorMapping exist then we can apply it, if not let's use the legacy coloring method
+    layer.colorMapping && splitColumnIds.length > 0
+      ? getColorSeriesAccessorFn(
+          JSON.parse(layer.colorMapping), // the color mapping is at this point just a strinfigied JSON
+          getPalette(AVAILABLE_PALETTES, NeutralPalette),
+          isDarkMode,
+          {
+            type: 'categories',
+            categories: getColorCategories(table.rows, splitColumnIds[0]),
+          },
+          splitColumnIds[0],
+          SPECIAL_TOKENS_STRING_CONVERTION
+        )
+      : (series) =>
+          getColor(
+            series,
+            {
+              layer,
+              colorAssignments,
+              paletteService,
+              getSeriesNameFn,
+              syncColors,
+            },
+            uiState,
+            singleTable
+          );
+
   return {
     splitSeriesAccessors: splitColumnIds.length ? splitColumnIds : [],
     stackAccessors: isStacked ? [xColumnId || 'unifiedX'] : [],
@@ -497,19 +536,7 @@ export const getSeriesProps: GetSeriesPropsFn = ({
       formatter?.id === 'bytes' && scaleType === ScaleType.Linear
         ? ScaleType.LinearBinary
         : scaleType,
-    color: (series) =>
-      getColor(
-        series,
-        {
-          layer,
-          colorAssignments,
-          paletteService,
-          getSeriesNameFn,
-          syncColors,
-        },
-        uiState,
-        singleTable
-      ),
+    color: colorAccessorFn,
     groupId: yAxis?.groupId,
     enableHistogramMode,
     stackMode,
diff --git a/src/plugins/charts/kibana.jsonc b/src/plugins/charts/kibana.jsonc
index 6b0e952969329..8c00cd40f4ad3 100644
--- a/src/plugins/charts/kibana.jsonc
+++ b/src/plugins/charts/kibana.jsonc
@@ -7,7 +7,8 @@
     "server": true,
     "browser": true,
     "requiredPlugins": [
-      "expressions"
+      "expressions",
+      "data"
     ],
     "extraPublicDirs": [
       "common"
diff --git a/src/plugins/data/common/search/aggs/buckets/index.ts b/src/plugins/data/common/search/aggs/buckets/index.ts
index 31bc7cf9ca544..369e56caf1859 100644
--- a/src/plugins/data/common/search/aggs/buckets/index.ts
+++ b/src/plugins/data/common/search/aggs/buckets/index.ts
@@ -37,7 +37,7 @@ export * from './significant_text_fn';
 export * from './significant_text';
 export * from './terms_fn';
 export * from './terms';
-export { MultiFieldKey } from './multi_field_key';
+export { MultiFieldKey, isMultiFieldKey, MULTI_FIELD_KEY_SEPARATOR } from './multi_field_key';
 export * from './multi_terms_fn';
 export * from './multi_terms';
 export * from './rare_terms_fn';
diff --git a/src/plugins/data/common/search/aggs/buckets/multi_field_key.ts b/src/plugins/data/common/search/aggs/buckets/multi_field_key.ts
index 89ac1f4c00a54..5b02d0d8827f2 100644
--- a/src/plugins/data/common/search/aggs/buckets/multi_field_key.ts
+++ b/src/plugins/data/common/search/aggs/buckets/multi_field_key.ts
@@ -38,3 +38,13 @@ export class MultiFieldKey {
     return this[id];
   }
 }
+
+export function isMultiFieldKey(field: unknown): field is MultiFieldKey {
+  return field instanceof MultiFieldKey;
+}
+
+/**
+ * Multi-field key separator used in Visualizations (Lens, AggBased, TSVB).
+ * This differs from the separator used in the toString method of the MultiFieldKey
+ */
+export const MULTI_FIELD_KEY_SEPARATOR = ' › ';
diff --git a/test/interpreter_functional/snapshots/baseline/partial_test_1.json b/test/interpreter_functional/snapshots/baseline/partial_test_1.json
index c7bb37566b0fe..90528b3321d22 100644
--- a/test/interpreter_functional/snapshots/baseline/partial_test_1.json
+++ b/test/interpreter_functional/snapshots/baseline/partial_test_1.json
@@ -1 +1 @@
-{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"isPreview":false,"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"colorMapping":null,"isPreview":false,"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json
index ac809c756d2cc..4d94b530c86e2 100644
--- a/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json
+++ b/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json
@@ -1 +1 @@
-{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"isPreview":false,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"colorMapping":null,"isPreview":false,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_empty_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_empty_data.json
index 6b52c8de57ae5..8c4e9fc5cd523 100644
--- a/test/interpreter_functional/snapshots/baseline/tagcloud_empty_data.json
+++ b/test/interpreter_functional/snapshots/baseline/tagcloud_empty_data.json
@@ -1 +1 @@
-{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[],"type":"datatable"},"visParams":{"ariaLabel":null,"isPreview":false,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[],"type":"datatable"},"visParams":{"ariaLabel":null,"colorMapping":null,"isPreview":false,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json b/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json
index 21e213ebb9c27..f142588711a31 100644
--- a/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json
+++ b/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json
@@ -1 +1 @@
-{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"isPreview":false,"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"colorMapping":null,"isPreview":false,"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json
index afaac18bf342d..291e6b40e6bfd 100644
--- a/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json
+++ b/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json
@@ -1 +1 @@
-{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"isPreview":false,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"colorMapping":null,"isPreview":false,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_options.json b/test/interpreter_functional/snapshots/baseline/tagcloud_options.json
index 03f74cc01d3e3..381d3afc54067 100644
--- a/test/interpreter_functional/snapshots/baseline/tagcloud_options.json
+++ b/test/interpreter_functional/snapshots/baseline/tagcloud_options.json
@@ -1 +1 @@
-{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"isPreview":false,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"log","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"colorMapping":null,"isPreview":false,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"log","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/partial_test_1.json b/test/interpreter_functional/snapshots/session/partial_test_1.json
index 6e12a10d1e283..90528b3321d22 100644
--- a/test/interpreter_functional/snapshots/session/partial_test_1.json
+++ b/test/interpreter_functional/snapshots/session/partial_test_1.json
@@ -1 +1 @@
-{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"colorMapping":null,"isPreview":false,"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/tagcloud_all_data.json b/test/interpreter_functional/snapshots/session/tagcloud_all_data.json
index cb14c6ea89407..4d94b530c86e2 100644
--- a/test/interpreter_functional/snapshots/session/tagcloud_all_data.json
+++ b/test/interpreter_functional/snapshots/session/tagcloud_all_data.json
@@ -1 +1 @@
-{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"colorMapping":null,"isPreview":false,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/tagcloud_empty_data.json b/test/interpreter_functional/snapshots/session/tagcloud_empty_data.json
index 0910e67409423..8c4e9fc5cd523 100644
--- a/test/interpreter_functional/snapshots/session/tagcloud_empty_data.json
+++ b/test/interpreter_functional/snapshots/session/tagcloud_empty_data.json
@@ -1 +1 @@
-{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[],"type":"datatable"},"visParams":{"ariaLabel":null,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[],"type":"datatable"},"visParams":{"ariaLabel":null,"colorMapping":null,"isPreview":false,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json b/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json
index 21e213ebb9c27..f142588711a31 100644
--- a/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json
+++ b/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json
@@ -1 +1 @@
-{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"isPreview":false,"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"colorMapping":null,"isPreview":false,"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json b/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json
index f340c5b653e35..291e6b40e6bfd 100644
--- a/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json
+++ b/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json
@@ -1 +1 @@
-{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"colorMapping":null,"isPreview":false,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/tagcloud_options.json b/test/interpreter_functional/snapshots/session/tagcloud_options.json
index ecbafbbc0afba..381d3afc54067 100644
--- a/test/interpreter_functional/snapshots/session/tagcloud_options.json
+++ b/test/interpreter_functional/snapshots/session/tagcloud_options.json
@@ -1 +1 @@
-{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"log","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"colorMapping":null,"isPreview":false,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"log","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/x-pack/plugins/lens/common/types.ts b/x-pack/plugins/lens/common/types.ts
index ff269070a18c2..34baa25120e09 100644
--- a/x-pack/plugins/lens/common/types.ts
+++ b/x-pack/plugins/lens/common/types.ts
@@ -8,7 +8,7 @@
 import type { Filter, FilterMeta } from '@kbn/es-query';
 import type { Position } from '@elastic/charts';
 import type { $Values } from '@kbn/utility-types';
-import type { CustomPaletteParams, PaletteOutput } from '@kbn/coloring';
+import { CustomPaletteParams, PaletteOutput, ColorMapping } from '@kbn/coloring';
 import type { ColorMode } from '@kbn/charts-plugin/common';
 import type { LegendSize } from '@kbn/visualizations-plugin/common';
 import { CategoryDisplay, LegendDisplay, NumberDisplay, PieChartTypes } from './constants';
@@ -71,6 +71,7 @@ export interface SharedPieLayerState {
   legendMaxLines?: number;
   legendSize?: LegendSize;
   truncateLegend?: boolean;
+  colorMapping?: ColorMapping.Config;
 }
 
 export type PieLayerState = SharedPieLayerState & {
diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx
index 22c5a21ad3377..d68476598ad99 100644
--- a/x-pack/plugins/lens/public/app_plugin/app.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/app.tsx
@@ -154,7 +154,7 @@ export function App({
 
   useExecutionContext(executionContext, {
     type: 'application',
-    id: savedObjectId || 'new',
+    id: savedObjectId || 'new', // TODO: this doesn't consider when lens is saved by value
     page: 'editor',
   });
 
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts
index 0e677804d5f16..d9cfa8c84c62f 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts
@@ -18,6 +18,7 @@ import {
   type EventAnnotationGroupConfig,
   EVENT_ANNOTATION_GROUP_TYPE,
 } from '@kbn/event-annotation-common';
+import { DEFAULT_COLOR_MAPPING_CONFIG } from '@kbn/coloring';
 import type {
   Datasource,
   DatasourceMap,
@@ -289,7 +290,8 @@ export function initializeVisualization({
       visualizationMap[visualizationState.activeId]?.initialize(
         () => '',
         visualizationState.state,
-        undefined,
+        // initialize a new visualization always with the new color mapping
+        { type: 'colorMapping', value: { ...DEFAULT_COLOR_MAPPING_CONFIG } },
         annotationGroups,
         references
       ) ?? visualizationState.state
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts
index e86f602465584..235e3b34538b8 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts
@@ -457,7 +457,13 @@ describe('suggestion helpers', () => {
   it('should pass passed in main palette if specified', () => {
     const mockVisualization1 = createMockVisualization();
     const mockVisualization2 = createMockVisualization();
-    const mainPalette: PaletteOutput = { type: 'palette', name: 'mock' };
+    const mainPalette: { type: 'legacyPalette'; value: PaletteOutput } = {
+      type: 'legacyPalette',
+      value: {
+        type: 'palette',
+        name: 'mock',
+      },
+    };
     datasourceMap.mock.getDatasourceSuggestionsFromCurrentState.mockReturnValue([
       generateSuggestion(0),
       generateSuggestion(1),
@@ -490,7 +496,13 @@ describe('suggestion helpers', () => {
   it('should query active visualization for main palette if not specified', () => {
     const mockVisualization1 = createMockVisualization();
     const mockVisualization2 = createMockVisualization();
-    const mainPalette: PaletteOutput = { type: 'palette', name: 'mock' };
+    const mainPalette: { type: 'legacyPalette'; value: PaletteOutput } = {
+      type: 'legacyPalette',
+      value: {
+        type: 'palette',
+        name: 'mock',
+      },
+    };
     mockVisualization1.getMainPalette = jest.fn(() => mainPalette);
     datasourceMap.mock.getDatasourceSuggestionsFromCurrentState.mockReturnValue([
       generateSuggestion(0),
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts
index 6679e8b042480..c1032d144ac33 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts
@@ -6,12 +6,11 @@
  */
 
 import type { Datatable } from '@kbn/expressions-plugin/common';
-import type { PaletteOutput } from '@kbn/coloring';
 import type { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public';
 import { LayerTypes } from '@kbn/expression-xy-plugin/public';
 import type { DragDropIdentifier } from '@kbn/dom-drag-drop';
 import { showMemoizedErrorNotification } from '../../lens_ui_errors';
-import type {
+import {
   Visualization,
   Datasource,
   TableSuggestion,
@@ -21,6 +20,7 @@ import type {
   VisualizeEditorContext,
   Suggestion,
   DatasourceLayers,
+  SuggestionRequest,
 } from '../../types';
 import type { LayerType } from '../../../common/types';
 import {
@@ -64,7 +64,7 @@ export function getSuggestions({
   visualizeTriggerFieldContext?: VisualizeFieldContext | VisualizeEditorContext;
   activeData?: Record<string, Datatable>;
   dataViews: DataViewsState;
-  mainPalette?: PaletteOutput;
+  mainPalette?: SuggestionRequest['mainPalette'];
   allowMixed?: boolean;
 }): Suggestion[] {
   const datasources = Object.entries(datasourceMap).filter(
@@ -237,7 +237,7 @@ function getVisualizationSuggestions(
   datasourceSuggestion: DatasourceSuggestion & { datasourceId: string },
   currentVisualizationState: unknown,
   subVisualizationId?: string,
-  mainPalette?: PaletteOutput,
+  mainPalette?: SuggestionRequest['mainPalette'],
   isFromContext?: boolean,
   activeData?: Record<string, Datatable>,
   allowMixed?: boolean
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx
index 5b14bb43dfb16..3e613d5a23e89 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx
@@ -519,7 +519,10 @@ describe('chart_switch', () => {
   it('should query main palette from active chart and pass into suggestions', async () => {
     const visualizationMap = mockVisualizationMap();
     const mockPalette: PaletteOutput = { type: 'palette', name: 'mock' };
-    visualizationMap.visA.getMainPalette = jest.fn(() => mockPalette);
+    visualizationMap.visA.getMainPalette = jest.fn(() => ({
+      type: 'legacyPalette',
+      value: mockPalette,
+    }));
     visualizationMap.visB.getSuggestions.mockReturnValueOnce([]);
     const frame = mockFrame(['a', 'b', 'c']);
     const currentVisState = {};
@@ -550,7 +553,7 @@ describe('chart_switch', () => {
     expect(visualizationMap.visB.getSuggestions).toHaveBeenCalledWith(
       expect.objectContaining({
         keptLayerIds: ['a'],
-        mainPalette: mockPalette,
+        mainPalette: { type: 'legacyPalette', value: mockPalette },
       })
     );
   });
diff --git a/x-pack/plugins/lens/public/shared_components/coloring/palette_panel_container.tsx b/x-pack/plugins/lens/public/shared_components/coloring/palette_panel_container.tsx
index b910354f1f68d..523c98c9d6903 100644
--- a/x-pack/plugins/lens/public/shared_components/coloring/palette_panel_container.tsx
+++ b/x-pack/plugins/lens/public/shared_components/coloring/palette_panel_container.tsx
@@ -27,8 +27,10 @@ export function PalettePanelContainer({
   handleClose,
   siblingRef,
   children,
+  title,
 }: {
   isOpen: boolean;
+  title: string;
   handleClose: () => void;
   siblingRef: MutableRefObject<HTMLDivElement | null>;
   children?: React.ReactElement | React.ReactElement[];
@@ -76,16 +78,12 @@ export function PalettePanelContainer({
 
                 <EuiFlexItem>
                   <EuiTitle size="xs">
-                    <h2
+                    <h3
                       id="lnsPalettePanelContainerTitle"
                       className="lnsPalettePanelContainer__headerTitle"
                     >
-                      <strong>
-                        {i18n.translate('xpack.lens.table.palettePanelTitle', {
-                          defaultMessage: 'Color',
-                        })}
-                      </strong>
-                    </h2>
+                      {title}
+                    </h3>
                   </EuiTitle>
                 </EuiFlexItem>
               </EuiFlexGroup>
diff --git a/x-pack/plugins/lens/public/shared_components/palette_picker.tsx b/x-pack/plugins/lens/public/shared_components/palette_picker.tsx
index efd1caba7e4da..51977e551128e 100644
--- a/x-pack/plugins/lens/public/shared_components/palette_picker.tsx
+++ b/x-pack/plugins/lens/public/shared_components/palette_picker.tsx
@@ -36,28 +36,24 @@ export function PalettePicker({
     });
   return (
     <EuiFormRow
-      display="columnCompressed"
       fullWidth
       label={i18n.translate('xpack.lens.palettePicker.label', {
-        defaultMessage: 'Color palette',
+        defaultMessage: 'Palette',
       })}
     >
-      <>
-        <EuiColorPalettePicker
-          fullWidth
-          data-test-subj="lns-palettePicker"
-          compressed
-          palettes={palettesToShow}
-          onChange={(newPalette) => {
-            setPalette({
-              type: 'palette',
-              name: newPalette,
-            });
-          }}
-          valueOfSelected={activePalette?.name || 'default'}
-          selectionDisplay={'palette'}
-        />
-      </>
+      <EuiColorPalettePicker
+        fullWidth
+        data-test-subj="lns-palettePicker"
+        palettes={palettesToShow}
+        onChange={(newPalette) => {
+          setPalette({
+            type: 'palette',
+            name: newPalette,
+          });
+        }}
+        valueOfSelected={activePalette?.name || 'default'}
+        selectionDisplay={'palette'}
+      />
     </EuiFormRow>
   );
 }
diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts
index d549fbb71bdcf..0c09d84df9adc 100644
--- a/x-pack/plugins/lens/public/types.ts
+++ b/x-pack/plugins/lens/public/types.ts
@@ -7,7 +7,7 @@
 import type { Ast } from '@kbn/interpreter';
 import type { IconType } from '@elastic/eui/src/components/icon/icon';
 import type { CoreStart, SavedObjectReference, ResolvedSimpleSavedObject } from '@kbn/core/public';
-import type { PaletteOutput } from '@kbn/coloring';
+import type { ColorMapping, PaletteOutput } from '@kbn/coloring';
 import type { TopNavMenuData } from '@kbn/navigation-plugin/public';
 import type { MutableRefObject, ReactElement } from 'react';
 import type { Filter, TimeRange } from '@kbn/es-query';
@@ -863,7 +863,12 @@ export interface SuggestionRequest<T = unknown> {
    * State is only passed if the visualization is active.
    */
   state?: T;
-  mainPalette?: PaletteOutput;
+  /**
+   * Passing the legacy palette or the new color mapping if available
+   */
+  mainPalette?:
+    | { type: 'legacyPalette'; value: PaletteOutput }
+    | { type: 'colorMapping'; value: ColorMapping.Config };
   isFromContext?: boolean;
   /**
    * The visualization needs to know which table is being suggested
@@ -1026,11 +1031,15 @@ export interface Visualization<T = unknown, P = T, ExtraAppendLayerArg = unknown
    * - When using suggestions, the suggested state is passed in
    */
   initialize: {
-    (addNewLayer: () => string, nonPersistedState?: T, mainPalette?: PaletteOutput): T;
+    (
+      addNewLayer: () => string,
+      nonPersistedState?: T,
+      mainPalette?: SuggestionRequest['mainPalette']
+    ): T;
     (
       addNewLayer: () => string,
       persistedState: P,
-      mainPalette?: PaletteOutput,
+      mainPalette?: SuggestionRequest['mainPalette'],
       annotationGroups?: AnnotationGroups,
       references?: SavedObjectReference[]
     ): T;
@@ -1042,7 +1051,7 @@ export interface Visualization<T = unknown, P = T, ExtraAppendLayerArg = unknown
    */
   getUsedDataViews?: (state?: T) => string[];
 
-  getMainPalette?: (state: T) => undefined | PaletteOutput;
+  getMainPalette?: (state: T) => undefined | SuggestionRequest['mainPalette'];
 
   /**
    * Supported triggers of this visualization type when embedded somewhere
diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.tsx
index 09f1bc93d6779..60646bdb8d054 100644
--- a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.tsx
+++ b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.tsx
@@ -253,6 +253,9 @@ export function TableDimensionEditor(
                     siblingRef={props.panelRef}
                     isOpen={isPaletteOpen}
                     handleClose={() => setIsPaletteOpen(!isPaletteOpen)}
+                    title={i18n.translate('xpack.lens.table.colorByRangePanelTitle', {
+                      defaultMessage: 'Color',
+                    })}
                   >
                     <CustomizablePalette
                       palettes={props.paletteService}
diff --git a/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx
index 83d6a7d532443..b30c613eea9d9 100644
--- a/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx
+++ b/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx
@@ -159,6 +159,9 @@ export function GaugeDimensionEditor(
                   siblingRef={props.panelRef}
                   isOpen={isPaletteOpen}
                   handleClose={togglePalette}
+                  title={i18n.translate('xpack.lens.table.colorByRangePanelTitle', {
+                    defaultMessage: 'Color',
+                  })}
                 >
                   <CustomizablePalette
                     palettes={props.paletteService}
diff --git a/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx b/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx
index b943e9601f24b..ac78d37ca28e9 100644
--- a/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx
+++ b/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx
@@ -224,7 +224,7 @@ export const getGaugeVisualization = ({
         layerId: addNewLayer(),
         layerType: LayerTypes.DATA,
         shape: GaugeShapes.HORIZONTAL_BULLET,
-        palette: mainPalette,
+        palette: mainPalette?.type === 'legacyPalette' ? mainPalette.value : undefined,
         ticksPosition: 'auto',
         labelMajorMode: 'auto',
       }
diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/heatmap/dimension_editor.tsx
index ef0700b7ca9fe..41fa319c2bdbf 100644
--- a/x-pack/plugins/lens/public/visualizations/heatmap/dimension_editor.tsx
+++ b/x-pack/plugins/lens/public/visualizations/heatmap/dimension_editor.tsx
@@ -88,6 +88,9 @@ export function HeatmapDimensionEditor(
               siblingRef={props.panelRef}
               isOpen={isPaletteOpen}
               handleClose={() => setIsPaletteOpen(!isPaletteOpen)}
+              title={i18n.translate('xpack.lens.table.colorByRangePanelTitle', {
+                defaultMessage: 'Color',
+              })}
             >
               {activePalette && (
                 <CustomizablePalette
diff --git a/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.tsx
index 84e0dc6f675b0..75500e5a3e9aa 100644
--- a/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.tsx
+++ b/x-pack/plugins/lens/public/visualizations/legacy_metric/dimension_editor.tsx
@@ -185,6 +185,9 @@ export function MetricDimensionEditor(
                 siblingRef={props.panelRef}
                 isOpen={isPaletteOpen}
                 handleClose={togglePalette}
+                title={i18n.translate('xpack.lens.table.colorByRangePanelTitle', {
+                  defaultMessage: 'Color',
+                })}
               >
                 <CustomizablePalette
                   palettes={props.paletteService}
diff --git a/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx
index 8db31f39c72de..09ca5149a38e2 100644
--- a/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx
+++ b/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx
@@ -360,6 +360,9 @@ function PrimaryMetricEditor(props: SubProps) {
                 siblingRef={props.panelRef}
                 isOpen={isPaletteOpen}
                 handleClose={togglePalette}
+                title={i18n.translate('xpack.lens.table.colorByRangePanelTitle', {
+                  defaultMessage: 'Color',
+                })}
               >
                 <CustomizablePalette
                   palettes={props.paletteService}
diff --git a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx
index 29831347b4954..4dee7938da049 100644
--- a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx
+++ b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx
@@ -369,7 +369,7 @@ export const getMetricVisualization = ({
       state ?? {
         layerId: addNewLayer(),
         layerType: layerTypes.DATA,
-        palette: mainPalette,
+        palette: mainPalette?.type === 'legacyPalette' ? mainPalette.value : undefined,
       }
     );
   },
diff --git a/x-pack/plugins/lens/public/visualizations/partition/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/partition/dimension_editor.tsx
index bea5c7668e9fe..7274f170c4336 100644
--- a/x-pack/plugins/lens/public/visualizations/partition/dimension_editor.tsx
+++ b/x-pack/plugins/lens/public/visualizations/partition/dimension_editor.tsx
@@ -8,20 +8,42 @@
 import './toolbar.scss';
 import React from 'react';
 import { i18n } from '@kbn/i18n';
-import type { PaletteRegistry } from '@kbn/coloring';
+import {
+  CategoricalColorMapping,
+  DEFAULT_COLOR_MAPPING_CONFIG,
+  PaletteRegistry,
+  ColorMapping,
+  SPECIAL_TOKENS_STRING_CONVERTION,
+  AVAILABLE_PALETTES,
+  getColorsFromMapping,
+} from '@kbn/coloring';
 import { ColorPicker, useDebouncedValue } from '@kbn/visualization-ui-components';
+import {
+  EuiFormRow,
+  EuiButtonIcon,
+  EuiColorPaletteDisplay,
+  EuiFlexGroup,
+  EuiFlexItem,
+  EuiSwitch,
+  EuiText,
+  EuiBadge,
+} from '@elastic/eui';
+import { useState, useCallback } from 'react';
+import { getColorCategories } from '@kbn/chart-expressions-common';
 import { PieVisualizationState } from '../../../common/types';
 import { VisualizationDimensionEditorProps } from '../../types';
-import { PalettePicker } from '../../shared_components';
+import { PalettePanelContainer, PalettePicker } from '../../shared_components';
 import { CollapseSetting } from '../../shared_components/collapse_setting';
 import {
   getDefaultColorForMultiMetricDimension,
   hasNonCollapsedSliceBy,
   isCollapsed,
 } from './visualization';
+import { trackUiCounterEvents } from '../../lens_ui_telemetry';
 
 type DimensionEditorProps = VisualizationDimensionEditorProps<PieVisualizationState> & {
   paletteService: PaletteRegistry;
+  isDarkMode: boolean;
 };
 
 export function DimensionEditor(props: DimensionEditorProps) {
@@ -30,10 +52,14 @@ export function DimensionEditor(props: DimensionEditorProps) {
       value: props.state,
       onChange: props.setState,
     });
+  const [isPaletteOpen, setIsPaletteOpen] = useState(false);
 
   const currentLayer = localState.layers.find((layer) => layer.layerId === props.layerId);
 
-  const setConfig = React.useCallback(
+  const canUseColorMapping = currentLayer && currentLayer.colorMapping ? true : false;
+  const [useNewColorMapping, setUseNewColorMapping] = useState(canUseColorMapping);
+
+  const setConfig = useCallback(
     ({ color }) => {
       if (!currentLayer) {
         return;
@@ -61,6 +87,23 @@ export function DimensionEditor(props: DimensionEditorProps) {
     [currentLayer, localState, props.accessor, setLocalState]
   );
 
+  const setColorMapping = useCallback(
+    (colorMapping?: ColorMapping.Config) => {
+      setLocalState({
+        ...localState,
+        layers: localState.layers.map((layer) =>
+          layer.layerId === currentLayer?.layerId
+            ? {
+                ...layer,
+                colorMapping,
+              }
+            : layer
+        ),
+      });
+    },
+    [localState, currentLayer, setLocalState]
+  );
+
   if (!currentLayer) {
     return null;
   }
@@ -84,17 +127,125 @@ export function DimensionEditor(props: DimensionEditorProps) {
         })
     : undefined;
 
+  const colors = getColorsFromMapping(props.isDarkMode, currentLayer.colorMapping);
+  const table = props.frame.activeData?.[currentLayer.layerId];
+  const splitCategories = getColorCategories(table?.rows ?? [], props.accessor);
+
   return (
     <>
       {props.accessor === firstNonCollapsedColumnId && (
-        <PalettePicker
-          palettes={props.paletteService}
-          activePalette={props.state.palette}
-          setPalette={(newPalette) => {
-            setLocalState({ ...props.state, palette: newPalette });
-          }}
-        />
+        <EuiFormRow
+          display="columnCompressed"
+          label={i18n.translate('xpack.lens.colorMapping.editColorMappingSectionlabel', {
+            defaultMessage: 'Color mapping',
+          })}
+          style={{ alignItems: 'center' }}
+        >
+          <EuiFlexGroup
+            alignItems="center"
+            gutterSize="s"
+            responsive={false}
+            className="lnsDynamicColoringClickable"
+          >
+            <EuiFlexItem>
+              <EuiColorPaletteDisplay
+                data-test-subj="lns_dynamicColoring_edit"
+                palette={colors}
+                type={'fixed'}
+                onClick={() => {
+                  setIsPaletteOpen(!isPaletteOpen);
+                }}
+              />
+            </EuiFlexItem>
+            <EuiFlexItem grow={false}>
+              <EuiButtonIcon
+                data-test-subj="lns_colorEditing_trigger"
+                aria-label={i18n.translate('xpack.lens.colorMapping.editColorMappingButton', {
+                  defaultMessage: 'Edit palette',
+                })}
+                iconType="controlsHorizontal"
+                onClick={() => {
+                  setIsPaletteOpen(!isPaletteOpen);
+                }}
+                size="xs"
+              />
+              <PalettePanelContainer
+                siblingRef={props.panelRef}
+                isOpen={isPaletteOpen}
+                handleClose={() => setIsPaletteOpen(!isPaletteOpen)}
+                title={
+                  useNewColorMapping
+                    ? i18n.translate('xpack.lens.colorMapping.editColorMappingTitle', {
+                        defaultMessage: 'Edit colors by term mapping',
+                      })
+                    : i18n.translate('xpack.lens.colorMapping.editColorsTitle', {
+                        defaultMessage: 'Edit colors',
+                      })
+                }
+              >
+                <div className="lnsPalettePanel__section lnsPalettePanel__section--shaded lnsIndexPatternDimensionEditor--padded">
+                  <EuiFlexGroup direction="column" gutterSize="s" justifyContent="flexStart">
+                    <EuiFlexItem>
+                      <EuiSwitch
+                        label={
+                          <EuiText size="xs">
+                            <span>
+                              {i18n.translate('xpack.lens.colorMapping.tryLabel', {
+                                defaultMessage: 'Use the new Color Mapping feature',
+                              })}{' '}
+                              <EuiBadge color="hollow">
+                                {i18n.translate('xpack.lens.colorMapping.techPreviewLabel', {
+                                  defaultMessage: 'Tech preview',
+                                })}
+                              </EuiBadge>
+                            </span>
+                          </EuiText>
+                        }
+                        data-test-subj="lns_colorMappingOrLegacyPalette_switch"
+                        compressed
+                        checked={useNewColorMapping}
+                        onChange={({ target: { checked } }) => {
+                          trackUiCounterEvents(
+                            `color_mapping_switch_${checked ? 'enabled' : 'disabled'}`
+                          );
+                          setColorMapping(
+                            checked ? { ...DEFAULT_COLOR_MAPPING_CONFIG } : undefined
+                          );
+                          setUseNewColorMapping(checked);
+                        }}
+                      />
+                    </EuiFlexItem>
+                    <EuiFlexItem>
+                      {canUseColorMapping || useNewColorMapping ? (
+                        <CategoricalColorMapping
+                          isDarkMode={props.isDarkMode}
+                          model={currentLayer.colorMapping ?? { ...DEFAULT_COLOR_MAPPING_CONFIG }}
+                          onModelUpdate={(model: ColorMapping.Config) => setColorMapping(model)}
+                          palettes={AVAILABLE_PALETTES}
+                          data={{
+                            type: 'categories',
+                            categories: splitCategories,
+                          }}
+                          specialTokens={SPECIAL_TOKENS_STRING_CONVERTION}
+                        />
+                      ) : (
+                        <PalettePicker
+                          palettes={props.paletteService}
+                          activePalette={props.state.palette}
+                          setPalette={(newPalette) => {
+                            setLocalState({ ...props.state, palette: newPalette });
+                          }}
+                        />
+                      )}
+                    </EuiFlexItem>
+                  </EuiFlexGroup>
+                </div>
+              </PalettePanelContainer>
+            </EuiFlexItem>
+          </EuiFlexGroup>
+        </EuiFormRow>
       )}
+      {/* TODO: understand how this works  */}
       {showColorPicker && (
         <ColorPicker
           {...props}
diff --git a/x-pack/plugins/lens/public/visualizations/partition/suggestions.test.ts b/x-pack/plugins/lens/public/visualizations/partition/suggestions.test.ts
index faba5edb73298..6486c1a95558a 100644
--- a/x-pack/plugins/lens/public/visualizations/partition/suggestions.test.ts
+++ b/x-pack/plugins/lens/public/visualizations/partition/suggestions.test.ts
@@ -5,7 +5,7 @@
  * 2.0.
  */
 
-import type { PaletteOutput } from '@kbn/coloring';
+import { DEFAULT_COLOR_MAPPING_CONFIG, PaletteOutput } from '@kbn/coloring';
 import { suggestions } from './suggestions';
 import type { DataType, SuggestionRequest } from '../../types';
 import type { PieLayerState, PieVisualizationState } from '../../../common/types';
@@ -594,7 +594,6 @@ describe('suggestions', () => {
     });
 
     it('should keep passed in palette', () => {
-      const mainPalette: PaletteOutput = { type: 'palette', name: 'mock' };
       const results = suggestions({
         table: {
           layerId: 'first',
@@ -617,10 +616,13 @@ describe('suggestions', () => {
         },
         state: undefined,
         keptLayerIds: ['first'],
-        mainPalette,
+        mainPalette: {
+          type: 'legacyPalette',
+          value: { type: 'palette', name: 'mock' },
+        },
       });
 
-      expect(results[0].state.palette).toEqual(mainPalette);
+      expect(results[0].state.palette).toEqual({ type: 'palette', name: 'mock' });
     });
 
     it('should keep the layer settings and palette when switching from treemap', () => {
@@ -681,6 +683,7 @@ describe('suggestions', () => {
                 legendMaxLines: 1,
                 truncateLegend: true,
                 nestedLegend: true,
+                colorMapping: DEFAULT_COLOR_MAPPING_CONFIG,
               },
             ],
           },
@@ -1060,6 +1063,7 @@ describe('suggestions', () => {
                 Object {
                   "allowMultipleMetrics": false,
                   "categoryDisplay": "default",
+                  "colorMapping": undefined,
                   "layerId": "first",
                   "layerType": "data",
                   "legendDisplay": "show",
@@ -1169,6 +1173,7 @@ describe('suggestions', () => {
               "layers": Array [
                 Object {
                   "categoryDisplay": "default",
+                  "colorMapping": undefined,
                   "layerId": "first",
                   "layerType": "data",
                   "legendDisplay": "show",
diff --git a/x-pack/plugins/lens/public/visualizations/partition/suggestions.ts b/x-pack/plugins/lens/public/visualizations/partition/suggestions.ts
index f3dea7c54b989..e78c203670aec 100644
--- a/x-pack/plugins/lens/public/visualizations/partition/suggestions.ts
+++ b/x-pack/plugins/lens/public/visualizations/partition/suggestions.ts
@@ -7,6 +7,7 @@
 
 import { partition } from 'lodash';
 import { i18n } from '@kbn/i18n';
+import { DEFAULT_COLOR_MAPPING_CONFIG } from '@kbn/coloring';
 import type {
   SuggestionRequest,
   TableSuggestionColumn,
@@ -131,7 +132,7 @@ export function suggestions({
       score: state && !hasCustomSuggestionsExists(state.shape) ? 0.6 : 0.4,
       state: {
         shape: newShape,
-        palette: mainPalette || state?.palette,
+        palette: mainPalette?.type === 'legacyPalette' ? mainPalette.value : state?.palette,
         layers: [
           state?.layers[0]
             ? {
@@ -140,6 +141,11 @@ export function suggestions({
                 primaryGroups: groups.map((col) => col.columnId),
                 metrics: metricColumnIds,
                 layerType: layerTypes.DATA,
+                colorMapping: !mainPalette
+                  ? { ...DEFAULT_COLOR_MAPPING_CONFIG }
+                  : mainPalette?.type === 'colorMapping'
+                  ? mainPalette.value
+                  : state.layers[0].colorMapping,
               }
             : {
                 layerId: table.layerId,
@@ -150,6 +156,11 @@ export function suggestions({
                 legendDisplay: LegendDisplay.DEFAULT,
                 nestedLegend: false,
                 layerType: layerTypes.DATA,
+                colorMapping: !mainPalette
+                  ? { ...DEFAULT_COLOR_MAPPING_CONFIG }
+                  : mainPalette?.type === 'colorMapping'
+                  ? mainPalette.value
+                  : undefined,
               },
         ],
       },
@@ -196,7 +207,7 @@ export function suggestions({
       score: state?.shape === PieChartTypes.TREEMAP ? 0.7 : 0.5,
       state: {
         shape: PieChartTypes.TREEMAP,
-        palette: mainPalette || state?.palette,
+        palette: mainPalette?.type === 'legacyPalette' ? mainPalette.value : state?.palette,
         layers: [
           state?.layers[0]
             ? {
@@ -209,6 +220,10 @@ export function suggestions({
                     ? CategoryDisplay.DEFAULT
                     : state.layers[0].categoryDisplay,
                 layerType: layerTypes.DATA,
+                colorMapping:
+                  mainPalette?.type === 'colorMapping'
+                    ? mainPalette.value
+                    : state.layers[0].colorMapping,
               }
             : {
                 layerId: table.layerId,
@@ -219,6 +234,7 @@ export function suggestions({
                 legendDisplay: LegendDisplay.DEFAULT,
                 nestedLegend: false,
                 layerType: layerTypes.DATA,
+                colorMapping: mainPalette?.type === 'colorMapping' ? mainPalette.value : undefined,
               },
         ],
       },
@@ -243,7 +259,7 @@ export function suggestions({
       score: state?.shape === PieChartTypes.MOSAIC ? 0.7 : 0.5,
       state: {
         shape: PieChartTypes.MOSAIC,
-        palette: mainPalette || state?.palette,
+        palette: mainPalette?.type === 'legacyPalette' ? mainPalette.value : state?.palette,
         layers: [
           state?.layers[0]
             ? {
@@ -255,6 +271,10 @@ export function suggestions({
                 categoryDisplay: CategoryDisplay.DEFAULT,
                 layerType: layerTypes.DATA,
                 allowMultipleMetrics: false,
+                colorMapping:
+                  mainPalette?.type === 'colorMapping'
+                    ? mainPalette.value
+                    : state.layers[0].colorMapping,
               }
             : {
                 layerId: table.layerId,
@@ -267,6 +287,7 @@ export function suggestions({
                 nestedLegend: false,
                 layerType: layerTypes.DATA,
                 allowMultipleMetrics: false,
+                colorMapping: mainPalette?.type === 'colorMapping' ? mainPalette.value : undefined,
               },
         ],
       },
@@ -290,7 +311,7 @@ export function suggestions({
       score: state?.shape === PieChartTypes.WAFFLE ? 0.7 : 0.4,
       state: {
         shape: PieChartTypes.WAFFLE,
-        palette: mainPalette || state?.palette,
+        palette: mainPalette?.type === 'legacyPalette' ? mainPalette.value : state?.palette,
         layers: [
           state?.layers[0]
             ? {
@@ -301,6 +322,10 @@ export function suggestions({
                 secondaryGroups: [],
                 categoryDisplay: CategoryDisplay.DEFAULT,
                 layerType: layerTypes.DATA,
+                colorMapping:
+                  mainPalette?.type === 'colorMapping'
+                    ? mainPalette.value
+                    : state.layers[0].colorMapping,
               }
             : {
                 layerId: table.layerId,
@@ -311,6 +336,7 @@ export function suggestions({
                 legendDisplay: LegendDisplay.DEFAULT,
                 nestedLegend: false,
                 layerType: layerTypes.DATA,
+                colorMapping: mainPalette?.type === 'colorMapping' ? mainPalette.value : undefined,
               },
         ],
       },
diff --git a/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts b/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts
index c592f7d369eb3..29e5fd399d148 100644
--- a/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts
+++ b/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts
@@ -7,7 +7,7 @@
 
 import type { Ast } from '@kbn/interpreter';
 import { Position } from '@elastic/charts';
-import type { PaletteOutput, PaletteRegistry } from '@kbn/coloring';
+import { PaletteOutput, PaletteRegistry } from '@kbn/coloring';
 
 import { buildExpression, buildExpressionFunction } from '@kbn/expressions-plugin/public';
 import type {
@@ -175,7 +175,6 @@ const generateCommonArguments = (
   const datasource = datasourceLayers[layer.layerId];
   const columnToLabelMap = getColumnToLabelMap(layer.metrics, datasource);
   const sortedMetricAccessors = getSortedAccessorsForGroup(datasource, layer, 'metrics');
-
   return {
     labels: generateCommonLabelsAstArgs(state, attributes, layer, columnToLabelMap),
     buckets: operations
@@ -200,6 +199,7 @@ const generateCommonArguments = (
       layer.truncateLegend ?? getDefaultVisualValuesForLayer(state, datasourceLayers).truncateText,
     palette: generatePaletteAstArguments(paletteService, state.palette),
     addTooltip: true,
+    colorMapping: layer.colorMapping ? JSON.stringify(layer.colorMapping) : undefined,
   };
 };
 
diff --git a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx
index 6b4767b9177e5..429089f743c33 100644
--- a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx
+++ b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx
@@ -8,13 +8,19 @@
 import React from 'react';
 import { i18n } from '@kbn/i18n';
 import { FormattedMessage } from '@kbn/i18n-react';
-import type { PaletteRegistry } from '@kbn/coloring';
+import {
+  ColorMapping,
+  DEFAULT_COLOR_MAPPING_CONFIG,
+  PaletteRegistry,
+  getColorsFromMapping,
+} from '@kbn/coloring';
 import { ThemeServiceStart } from '@kbn/core/public';
 import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public';
 import { EuiSpacer } from '@elastic/eui';
 import { PartitionVisConfiguration } from '@kbn/visualizations-plugin/common/convert_to_lens';
 import { LayerTypes } from '@kbn/expression-xy-plugin/public';
 import { AccessorConfig } from '@kbn/visualization-ui-components';
+import useObservable from 'react-use/lib/useObservable';
 import type { FormBasedPersistedState } from '../../datasources/form_based/types';
 import type {
   Visualization,
@@ -51,7 +57,7 @@ const metricLabel = i18n.translate('xpack.lens.pie.groupMetricLabelSingular', {
   defaultMessage: 'Metric',
 });
 
-function newLayerState(layerId: string): PieLayerState {
+function newLayerState(layerId: string, colorMapping: ColorMapping.Config): PieLayerState {
   return {
     layerId,
     primaryGroups: [],
@@ -62,6 +68,7 @@ function newLayerState(layerId: string): PieLayerState {
     legendDisplay: LegendDisplay.DEFAULT,
     nestedLegend: false,
     layerType: LayerTypes.DATA,
+    colorMapping,
   };
 }
 
@@ -137,7 +144,9 @@ export const getPieVisualization = ({
   clearLayer(state) {
     return {
       shape: state.shape,
-      layers: state.layers.map((l) => newLayerState(l.layerId)),
+      layers: state.layers.map((l) =>
+        newLayerState(l.layerId, { ...DEFAULT_COLOR_MAPPING_CONFIG })
+      ),
     };
   },
 
@@ -156,13 +165,29 @@ export const getPieVisualization = ({
     return (
       state || {
         shape: PieChartTypes.DONUT,
-        layers: [newLayerState(addNewLayer())],
-        palette: mainPalette,
+        layers: [
+          newLayerState(
+            addNewLayer(),
+            mainPalette?.type === 'colorMapping'
+              ? mainPalette.value
+              : { ...DEFAULT_COLOR_MAPPING_CONFIG }
+          ),
+        ],
+        palette: mainPalette?.type === 'legacyPalette' ? mainPalette.value : undefined,
       }
     );
   },
 
-  getMainPalette: (state) => (state ? state.palette : undefined),
+  getMainPalette: (state) => {
+    if (!state) {
+      return undefined;
+    }
+    return state.layers.length > 0 && state.layers[0].colorMapping
+      ? { type: 'colorMapping', value: state.layers[0].colorMapping }
+      : state.palette
+      ? { type: 'legacyPalette', value: state.palette }
+      : undefined;
+  },
 
   getSuggestions: suggestions,
 
@@ -174,6 +199,19 @@ export const getPieVisualization = ({
 
     const datasource = frame.datasourceLayers[layer.layerId];
 
+    let colors: string[] = [];
+    kibanaTheme.theme$
+      .subscribe({
+        next(theme) {
+          colors = state.layers[0]?.colorMapping
+            ? getColorsFromMapping(theme.darkMode, state.layers[0].colorMapping)
+            : paletteService
+                .get(state.palette?.name || 'default')
+                .getCategoricalColors(10, state.palette?.params);
+        },
+      })
+      .unsubscribe();
+
     const getPrimaryGroupConfig = (): VisualizationDimensionGroupConfig => {
       const originalOrder = getSortedAccessorsForGroup(datasource, layer, 'primaryGroups');
       // When we add a column it could be empty, and therefore have no order
@@ -187,9 +225,7 @@ export const getPieVisualization = ({
       accessors.forEach((accessorConfig) => {
         if (firstNonCollapsedColumnId === accessorConfig.columnId) {
           accessorConfig.triggerIconType = 'colorBy';
-          accessorConfig.palette = paletteService
-            .get(state.palette?.name || 'default')
-            .getCategoricalColors(10, state.palette?.params);
+          accessorConfig.palette = colors;
         }
       });
 
@@ -459,7 +495,8 @@ export const getPieVisualization = ({
     };
   },
   DimensionEditorComponent(props) {
-    return <DimensionEditor {...props} paletteService={paletteService} />;
+    const isDarkMode = useObservable(kibanaTheme.theme$, { darkMode: false }).darkMode;
+    return <DimensionEditor {...props} paletteService={paletteService} isDarkMode={isDarkMode} />;
   },
   DimensionEditorDataExtraComponent(props) {
     return <DimensionDataExtraEditor {...props} paletteService={paletteService} />;
diff --git a/x-pack/plugins/lens/public/visualizations/tagcloud/index.ts b/x-pack/plugins/lens/public/visualizations/tagcloud/index.ts
index 129d8f4eb545f..e58f8fe673127 100644
--- a/x-pack/plugins/lens/public/visualizations/tagcloud/index.ts
+++ b/x-pack/plugins/lens/public/visualizations/tagcloud/index.ts
@@ -19,7 +19,7 @@ export class TagcloudVisualization {
     editorFrame.registerVisualization(async () => {
       const { getTagcloudVisualization } = await import('../../async_services');
       const palettes = await charts.palettes.getPalettes();
-      return getTagcloudVisualization({ paletteService: palettes, theme: core.theme });
+      return getTagcloudVisualization({ paletteService: palettes, kibanaTheme: core.theme });
     });
   }
 }
diff --git a/x-pack/plugins/lens/public/visualizations/tagcloud/suggestions.ts b/x-pack/plugins/lens/public/visualizations/tagcloud/suggestions.ts
index c85f7b0b28fe2..4a528c99d41ad 100644
--- a/x-pack/plugins/lens/public/visualizations/tagcloud/suggestions.ts
+++ b/x-pack/plugins/lens/public/visualizations/tagcloud/suggestions.ts
@@ -7,6 +7,7 @@
 
 import { partition } from 'lodash';
 import { IconChartTagcloud } from '@kbn/chart-icons';
+import { DEFAULT_COLOR_MAPPING_CONFIG } from '@kbn/coloring';
 import type { SuggestionRequest, VisualizationSuggestion } from '../../types';
 import type { TagcloudState } from './types';
 import { DEFAULT_STATE, TAGCLOUD_LABEL } from './constants';
@@ -48,6 +49,11 @@ export function getSuggestions({
           tagAccessor: bucket.columnId,
           valueAccessor: metrics[0].columnId,
           ...DEFAULT_STATE,
+          colorMapping: !mainPalette
+            ? { ...DEFAULT_COLOR_MAPPING_CONFIG }
+            : mainPalette?.type === 'colorMapping'
+            ? mainPalette.value
+            : undefined,
         },
       };
     });
diff --git a/x-pack/plugins/lens/public/visualizations/tagcloud/tagcloud_visualization.tsx b/x-pack/plugins/lens/public/visualizations/tagcloud/tagcloud_visualization.tsx
index d1bd1ec337bc0..c9449f2dad178 100644
--- a/x-pack/plugins/lens/public/visualizations/tagcloud/tagcloud_visualization.tsx
+++ b/x-pack/plugins/lens/public/visualizations/tagcloud/tagcloud_visualization.tsx
@@ -16,9 +16,10 @@ import {
   buildExpressionFunction,
   ExpressionFunctionTheme,
 } from '@kbn/expressions-plugin/common';
-import { PaletteRegistry } from '@kbn/coloring';
+import { PaletteRegistry, DEFAULT_COLOR_MAPPING_CONFIG, getColorsFromMapping } from '@kbn/coloring';
 import { IconChartTagcloud } from '@kbn/chart-icons';
 import { SystemPaletteExpressionFunctionDefinition } from '@kbn/charts-plugin/common';
+import useObservable from 'react-use/lib/useObservable';
 import type { OperationMetadata, Visualization } from '../..';
 import type { TagcloudState } from './types';
 import { getSuggestions } from './suggestions';
@@ -31,10 +32,10 @@ const METRIC_GROUP_ID = 'metric';
 
 export const getTagcloudVisualization = ({
   paletteService,
-  theme,
+  kibanaTheme,
 }: {
   paletteService: PaletteRegistry;
-  theme: ThemeServiceStart;
+  kibanaTheme: ThemeServiceStart;
 }): Visualization<TagcloudState> => ({
   id: 'lnsTagcloud',
 
@@ -89,6 +90,15 @@ export const getTagcloudVisualization = ({
           },
         };
   },
+  getMainPalette: (state) => {
+    if (!state) return;
+
+    return state.colorMapping
+      ? { type: 'colorMapping', value: state.colorMapping }
+      : state.palette
+      ? { type: 'legacyPalette', value: state.palette }
+      : undefined;
+  },
 
   triggers: [VIS_EVENT_TO_TRIGGER.filter],
 
@@ -98,11 +108,27 @@ export const getTagcloudVisualization = ({
         layerId: addNewLayer(),
         layerType: LayerTypes.DATA,
         ...DEFAULT_STATE,
+        colorMapping: { ...DEFAULT_COLOR_MAPPING_CONFIG },
       }
     );
   },
 
   getConfiguration({ state }) {
+    const canUseColorMapping = state.colorMapping ? true : false;
+    let colors: string[] = [];
+    if (canUseColorMapping) {
+      kibanaTheme.theme$
+        .subscribe({
+          next(theme) {
+            colors = getColorsFromMapping(theme.darkMode, state.colorMapping);
+          },
+        })
+        .unsubscribe();
+    } else {
+      colors = paletteService
+        .get(state.palette?.name || 'default')
+        .getCategoricalColors(10, state.palette?.params);
+    }
     return {
       groups: [
         {
@@ -116,9 +142,7 @@ export const getTagcloudVisualization = ({
                 {
                   columnId: state.tagAccessor,
                   triggerIconType: 'colorBy',
-                  palette: paletteService
-                    .get(state.palette?.name || 'default')
-                    .getCategoricalColors(10, state.palette?.params),
+                  palette: colors,
                 },
               ]
             : [],
@@ -197,6 +221,7 @@ export const getTagcloudVisualization = ({
                 ),
           ]).toAst(),
           showLabel: state.showLabel,
+          colorMapping: state.colorMapping ? JSON.stringify(state.colorMapping) : undefined,
         }).toAst(),
       ],
     };
@@ -235,6 +260,7 @@ export const getTagcloudVisualization = ({
                 ),
           ]).toAst(),
           showLabel: false,
+          colorMapping: state.colorMapping ? JSON.stringify(state.colorMapping) : undefined,
         }).toAst(),
       ],
     };
@@ -266,12 +292,16 @@ export const getTagcloudVisualization = ({
   },
 
   DimensionEditorComponent(props) {
+    const isDarkMode: boolean = useObservable(kibanaTheme.theme$, { darkMode: false }).darkMode;
     if (props.groupId === TAG_GROUP_ID) {
       return (
         <TagsDimensionEditor
+          isDarkMode={isDarkMode}
           paletteService={paletteService}
           state={props.state}
           setState={props.setState}
+          frame={props.frame}
+          panelRef={props.panelRef}
         />
       );
     }
diff --git a/x-pack/plugins/lens/public/visualizations/tagcloud/tags_dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/tagcloud/tags_dimension_editor.tsx
index e91a73982dd38..1728e6240ad9f 100644
--- a/x-pack/plugins/lens/public/visualizations/tagcloud/tags_dimension_editor.tsx
+++ b/x-pack/plugins/lens/public/visualizations/tagcloud/tags_dimension_editor.tsx
@@ -6,27 +6,196 @@
  */
 
 import React from 'react';
-import { PaletteRegistry } from '@kbn/coloring';
+import {
+  PaletteRegistry,
+  CategoricalColorMapping,
+  DEFAULT_COLOR_MAPPING_CONFIG,
+  ColorMapping,
+  SPECIAL_TOKENS_STRING_CONVERTION,
+  PaletteOutput,
+  AVAILABLE_PALETTES,
+  getColorsFromMapping,
+} from '@kbn/coloring';
+import { i18n } from '@kbn/i18n';
+import {
+  EuiButtonIcon,
+  EuiColorPaletteDisplay,
+  EuiFlexGroup,
+  EuiFlexItem,
+  EuiSwitch,
+  EuiFormRow,
+  EuiText,
+  EuiBadge,
+} from '@elastic/eui';
+import { useState, MutableRefObject, useCallback } from 'react';
+import { PalettePicker } from '@kbn/coloring/src/shared_components/coloring/palette_picker';
+import { useDebouncedValue } from '@kbn/visualization-ui-components';
+import { getColorCategories } from '@kbn/chart-expressions-common';
 import type { TagcloudState } from './types';
-import { PalettePicker } from '../../shared_components';
+import { PalettePanelContainer } from '../../shared_components';
+import { FramePublicAPI } from '../../types';
+import { trackUiCounterEvents } from '../../lens_ui_telemetry';
 
 interface Props {
   paletteService: PaletteRegistry;
   state: TagcloudState;
   setState: (state: TagcloudState) => void;
+  frame: FramePublicAPI;
+  panelRef: MutableRefObject<HTMLDivElement | null>;
+  isDarkMode: boolean;
 }
 
-export function TagsDimensionEditor(props: Props) {
+export function TagsDimensionEditor({
+  state,
+  frame,
+  setState,
+  panelRef,
+  isDarkMode,
+  paletteService,
+}: Props) {
+  const { inputValue: localState, handleInputChange: setLocalState } =
+    useDebouncedValue<TagcloudState>({
+      value: state,
+      onChange: setState,
+    });
+  const [isPaletteOpen, setIsPaletteOpen] = useState(false);
+  const [useNewColorMapping, setUseNewColorMapping] = useState(state.colorMapping ? true : false);
+
+  const colors = getColorsFromMapping(isDarkMode, state.colorMapping);
+  const table = frame.activeData?.[state.layerId];
+  const splitCategories = getColorCategories(table?.rows ?? [], state.tagAccessor);
+
+  const setColorMapping = useCallback(
+    (colorMapping?: ColorMapping.Config) => {
+      setLocalState({
+        ...localState,
+        colorMapping,
+      });
+    },
+    [localState, setLocalState]
+  );
+
+  const setPalette = useCallback(
+    (palette: PaletteOutput) => {
+      setLocalState({
+        ...localState,
+        palette,
+        colorMapping: undefined,
+      });
+    },
+    [localState, setLocalState]
+  );
+
+  const canUseColorMapping = state.colorMapping;
+
   return (
-    <PalettePicker
-      palettes={props.paletteService}
-      activePalette={props.state.palette}
-      setPalette={(newPalette) => {
-        props.setState({
-          ...props.state,
-          palette: newPalette,
-        });
-      }}
-    />
+    <EuiFormRow
+      display="columnCompressed"
+      label={i18n.translate('xpack.lens.colorMapping.editColorMappingSectionlabel', {
+        defaultMessage: 'Color mapping',
+      })}
+      style={{ alignItems: 'center' }}
+    >
+      <EuiFlexGroup
+        alignItems="center"
+        gutterSize="s"
+        responsive={false}
+        className="lnsDynamicColoringClickable"
+      >
+        <EuiFlexItem>
+          <EuiColorPaletteDisplay
+            data-test-subj="lns_dynamicColoring_edit"
+            palette={colors}
+            type={'fixed'}
+            onClick={() => {
+              setIsPaletteOpen(!isPaletteOpen);
+            }}
+          />
+        </EuiFlexItem>
+        <EuiFlexItem grow={false}>
+          <EuiButtonIcon
+            data-test-subj="lns_colorEditing_trigger"
+            aria-label={i18n.translate('xpack.lens.colorMapping.editColorMappingButton', {
+              defaultMessage: 'Edit palette',
+            })}
+            iconType="controlsHorizontal"
+            onClick={() => {
+              setIsPaletteOpen(!isPaletteOpen);
+            }}
+            size="xs"
+          />
+          <PalettePanelContainer
+            siblingRef={panelRef}
+            isOpen={isPaletteOpen}
+            handleClose={() => setIsPaletteOpen(!isPaletteOpen)}
+            title={
+              useNewColorMapping
+                ? i18n.translate('xpack.lens.colorMapping.editColorMappingTitle', {
+                    defaultMessage: 'Edit colors by term mapping',
+                  })
+                : i18n.translate('xpack.lens.colorMapping.editColorsTitle', {
+                    defaultMessage: 'Edit colors',
+                  })
+            }
+          >
+            <div className="lnsPalettePanel__section lnsPalettePanel__section--shaded lnsIndexPatternDimensionEditor--padded">
+              <EuiFlexGroup direction="column" gutterSize="s" justifyContent="flexStart">
+                <EuiFlexItem>
+                  <EuiSwitch
+                    label={
+                      <EuiText size="xs">
+                        <span>
+                          {i18n.translate('xpack.lens.colorMapping.tryLabel', {
+                            defaultMessage: 'Use the new Color Mapping feature',
+                          })}{' '}
+                          <EuiBadge color="hollow">
+                            {i18n.translate('xpack.lens.colorMapping.techPreviewLabel', {
+                              defaultMessage: 'Tech preview',
+                            })}
+                          </EuiBadge>
+                        </span>
+                      </EuiText>
+                    }
+                    data-test-subj="lns_colorMappingOrLegacyPalette_switch"
+                    compressed
+                    checked={useNewColorMapping}
+                    onChange={({ target: { checked } }) => {
+                      trackUiCounterEvents(
+                        `color_mapping_switch_${checked ? 'enabled' : 'disabled'}`
+                      );
+                      setColorMapping(checked ? { ...DEFAULT_COLOR_MAPPING_CONFIG } : undefined);
+                      setUseNewColorMapping(checked);
+                    }}
+                  />
+                </EuiFlexItem>
+                <EuiFlexItem>
+                  {canUseColorMapping || useNewColorMapping ? (
+                    <CategoricalColorMapping
+                      isDarkMode={isDarkMode}
+                      model={state.colorMapping ?? { ...DEFAULT_COLOR_MAPPING_CONFIG }}
+                      onModelUpdate={(model: ColorMapping.Config) => setColorMapping(model)}
+                      palettes={AVAILABLE_PALETTES}
+                      data={{
+                        type: 'categories',
+                        categories: splitCategories,
+                      }}
+                      specialTokens={SPECIAL_TOKENS_STRING_CONVERTION}
+                    />
+                  ) : (
+                    <PalettePicker
+                      palettes={paletteService}
+                      activePalette={state.palette}
+                      setPalette={(newPalette) => {
+                        setPalette(newPalette);
+                      }}
+                    />
+                  )}
+                </EuiFlexItem>
+              </EuiFlexGroup>
+            </div>
+          </PalettePanelContainer>
+        </EuiFlexItem>
+      </EuiFlexGroup>
+    </EuiFormRow>
   );
 }
diff --git a/x-pack/plugins/lens/public/visualizations/tagcloud/types.ts b/x-pack/plugins/lens/public/visualizations/tagcloud/types.ts
index c4a6ff1ddb6ad..afc83074c1fa2 100644
--- a/x-pack/plugins/lens/public/visualizations/tagcloud/types.ts
+++ b/x-pack/plugins/lens/public/visualizations/tagcloud/types.ts
@@ -7,7 +7,7 @@
 
 import { $Values } from '@kbn/utility-types';
 import { Datatable } from '@kbn/expressions-plugin/common';
-import type { PaletteOutput } from '@kbn/coloring';
+import { PaletteOutput, ColorMapping } from '@kbn/coloring';
 import { Orientation } from '@kbn/expression-tagcloud-plugin/common';
 
 export interface TagcloudState {
@@ -19,6 +19,7 @@ export interface TagcloudState {
   orientation: $Values<typeof Orientation>;
   palette?: PaletteOutput;
   showLabel: boolean;
+  colorMapping?: ColorMapping.Config;
 }
 
 export interface TagcloudConfig extends TagcloudState {
diff --git a/x-pack/plugins/lens/public/visualizations/xy/to_expression.ts b/x-pack/plugins/lens/public/visualizations/xy/to_expression.ts
index 4e8264a733398..2b80a39fc3b53 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/to_expression.ts
+++ b/x-pack/plugins/lens/public/visualizations/xy/to_expression.ts
@@ -7,7 +7,7 @@
 
 import { Ast } from '@kbn/interpreter';
 import { Position, ScaleType } from '@elastic/charts';
-import type { PaletteRegistry } from '@kbn/coloring';
+import { PaletteRegistry } from '@kbn/coloring';
 import {
   buildExpression,
   buildExpressionFunction,
@@ -511,6 +511,7 @@ const dataLayerToExpression = (
             name: 'default',
           }),
     ]).toAst(),
+    colorMapping: layer.colorMapping ? JSON.stringify(layer.colorMapping) : undefined,
   });
 
   return {
diff --git a/x-pack/plugins/lens/public/visualizations/xy/types.ts b/x-pack/plugins/lens/public/visualizations/xy/types.ts
index e863f04bcdc05..8961c38b1582a 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/types.ts
+++ b/x-pack/plugins/lens/public/visualizations/xy/types.ts
@@ -7,7 +7,7 @@
 
 import { i18n } from '@kbn/i18n';
 import { $Values } from '@kbn/utility-types';
-import type { PaletteOutput } from '@kbn/coloring';
+import type { ColorMapping, PaletteOutput } from '@kbn/coloring';
 import type {
   LegendConfig,
   AxisExtentConfig,
@@ -103,6 +103,7 @@ export interface XYDataLayerConfig {
   xScaleType?: XScaleType;
   isHistogram?: boolean;
   columnToLabel?: string;
+  colorMapping?: ColorMapping.Config;
 }
 
 export interface XYReferenceLineLayerConfig {
diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx
index e89b3a843e8ac..c9d52d43df7ef 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx
@@ -57,6 +57,7 @@ import {
 } from './visualization_helpers';
 import { cloneDeep } from 'lodash';
 import { DataViewsServicePublic } from '@kbn/data-views-plugin/public';
+import { EUIAmsterdamColorBlindPalette } from '@kbn/coloring';
 
 const DATE_HISTORGRAM_COLUMN_ID = 'date_histogram_column';
 const exampleAnnotation: EventAnnotationConfig = {
@@ -221,8 +222,30 @@ describe('xy_visualization', () => {
           "layers": Array [
             Object {
               "accessors": Array [],
+              "colorMapping": Object {
+                "assignmentMode": "auto",
+                "assignments": Array [],
+                "colorMode": Object {
+                  "type": "categorical",
+                },
+                "paletteId": "${EUIAmsterdamColorBlindPalette.id}",
+                "specialAssignments": Array [
+                  Object {
+                    "color": Object {
+                      "colorIndex": 1,
+                      "paletteId": "neutral",
+                      "type": "categorical",
+                    },
+                    "rule": Object {
+                      "type": "other",
+                    },
+                    "touched": false,
+                  },
+                ],
+              },
               "layerId": "l1",
               "layerType": "data",
+              "palette": undefined,
               "position": "top",
               "seriesType": "bar_stacked",
               "showGridlines": false,
diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx
index 9f5f9755d1781..812d74ddcb331 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx
@@ -25,6 +25,8 @@ import type { EventAnnotationGroupConfig } from '@kbn/event-annotation-common';
 import { isEqual } from 'lodash';
 import { type AccessorConfig, DimensionTrigger } from '@kbn/visualization-ui-components';
 import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
+import { DEFAULT_COLOR_MAPPING_CONFIG, getColorsFromMapping } from '@kbn/coloring';
+import useObservable from 'react-use/lib/useObservable';
 import { generateId } from '../../id_generator';
 import {
   isDraggedDataViewField,
@@ -254,7 +256,7 @@ export const getXyVisualization = ({
   initialize(
     addNewLayer,
     state,
-    _mainPalette?,
+    mainPalette?,
     annotationGroups?: AnnotationGroups,
     references?: SavedObjectReference[]
   ) {
@@ -276,6 +278,11 @@ export const getXyVisualization = ({
             seriesType: defaultSeriesType,
             showGridlines: false,
             layerType: LayerTypes.DATA,
+            palette: mainPalette?.type === 'legacyPalette' ? mainPalette.value : undefined,
+            colorMapping:
+              mainPalette?.type === 'colorMapping'
+                ? mainPalette.value
+                : { ...DEFAULT_COLOR_MAPPING_CONFIG },
           },
         ],
       }
@@ -416,6 +423,22 @@ export const getXyVisualization = ({
         }
       ).length < 2;
 
+    const canUseColorMapping = layer.colorMapping ? true : false;
+    let colors: string[] = [];
+    if (canUseColorMapping) {
+      kibanaTheme.theme$
+        .subscribe({
+          next(theme) {
+            colors = getColorsFromMapping(theme.darkMode, layer.colorMapping);
+          },
+        })
+        .unsubscribe();
+    } else {
+      colors = paletteService
+        .get(dataLayer.palette?.name || 'default')
+        .getCategoricalColors(10, dataLayer.palette?.params);
+    }
+
     return {
       groups: [
         {
@@ -447,11 +470,7 @@ export const getXyVisualization = ({
                 {
                   columnId: dataLayer.splitAccessor,
                   triggerIconType: dataLayer.collapseFn ? 'aggregate' : 'colorBy',
-                  palette: dataLayer.collapseFn
-                    ? undefined
-                    : paletteService
-                        .get(dataLayer.palette?.name || 'default')
-                        .getCategoricalColors(10, dataLayer.palette?.params),
+                  palette: dataLayer.collapseFn ? undefined : colors,
                 },
               ]
             : [],
@@ -469,7 +488,13 @@ export const getXyVisualization = ({
 
   getMainPalette: (state) => {
     if (!state || state.layers.length === 0) return;
-    return getFirstDataLayer(state.layers)?.palette;
+    const firstDataLayer = getFirstDataLayer(state.layers);
+
+    return firstDataLayer?.colorMapping
+      ? { type: 'colorMapping', value: firstDataLayer.colorMapping }
+      : firstDataLayer?.palette
+      ? { type: 'legacyPalette', value: firstDataLayer.palette }
+      : undefined;
   },
 
   getDropProps(dropProps) {
@@ -641,13 +666,15 @@ export const getXyVisualization = ({
       formatFactory: fieldFormats.deserialize,
       paletteService,
     };
+
+    const darkMode: boolean = useObservable(kibanaTheme.theme$, { darkMode: false }).darkMode;
     const layer = props.state.layers.find((l) => l.layerId === props.layerId)!;
     const dimensionEditor = isReferenceLayer(layer) ? (
       <ReferenceLinePanel {...allProps} />
     ) : isAnnotationsLayer(layer) ? (
       <AnnotationsPanel {...allProps} dataViewsService={dataViewsService} />
     ) : (
-      <DataDimensionEditor {...allProps} />
+      <DataDimensionEditor {...allProps} darkMode={darkMode} />
     );
 
     return dimensionEditor;
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/dimension_editor.tsx
index 94c7a326ccbb4..44114e8d560ed 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/dimension_editor.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/dimension_editor.tsx
@@ -5,21 +5,45 @@
  * 2.0.
  */
 
-import React, { useCallback, useMemo } from 'react';
+import React, { useCallback, useMemo, useState } from 'react';
 import { i18n } from '@kbn/i18n';
-import { EuiButtonGroup, EuiFormRow, htmlIdGenerator } from '@elastic/eui';
-import type { PaletteRegistry } from '@kbn/coloring';
 import { useDebouncedValue } from '@kbn/visualization-ui-components';
 import { ColorPicker } from '@kbn/visualization-ui-components';
+
+import {
+  EuiBadge,
+  EuiButtonGroup,
+  EuiButtonIcon,
+  EuiColorPaletteDisplay,
+  EuiFlexGroup,
+  EuiFlexItem,
+  EuiFormRow,
+  EuiSpacer,
+  EuiSwitch,
+  EuiText,
+  htmlIdGenerator,
+} from '@elastic/eui';
+import {
+  PaletteRegistry,
+  ColorMapping,
+  DEFAULT_COLOR_MAPPING_CONFIG,
+  CategoricalColorMapping,
+  PaletteOutput,
+  SPECIAL_TOKENS_STRING_CONVERTION,
+  AVAILABLE_PALETTES,
+  getColorsFromMapping,
+} from '@kbn/coloring';
+import { getColorCategories } from '@kbn/chart-expressions-common';
 import type { VisualizationDimensionEditorProps } from '../../../types';
 import { State, XYState, XYDataLayerConfig, YConfig, YAxisMode } from '../types';
 import { FormatFactory } from '../../../../common/types';
 import { getSeriesColor, isHorizontalChart } from '../state_helpers';
-import { PalettePicker } from '../../../shared_components';
+import { PalettePanelContainer, PalettePicker } from '../../../shared_components';
 import { getDataLayers } from '../visualization_helpers';
 import { CollapseSetting } from '../../../shared_components/collapse_setting';
 import { getSortedAccessors } from '../to_expression';
 import { getColorAssignments, getAssignedColorConfig } from '../color_assignment';
+import { trackUiCounterEvents } from '../../../lens_ui_telemetry';
 
 type UnwrapArray<T> = T extends Array<infer P> ? P : T;
 
@@ -43,11 +67,16 @@ export function DataDimensionEditor(
   props: VisualizationDimensionEditorProps<State> & {
     formatFactory: FormatFactory;
     paletteService: PaletteRegistry;
+    darkMode: boolean;
   }
 ) {
-  const { state, setState, layerId, accessor } = props;
+  const { state, layerId, accessor, darkMode } = props;
   const index = state.layers.findIndex((l) => l.layerId === layerId);
   const layer = state.layers[index] as XYDataLayerConfig;
+  const canUseColorMapping = layer.colorMapping ? true : false;
+
+  const [isPaletteOpen, setIsPaletteOpen] = useState(false);
+  const [useNewColorMapping, setUseNewColorMapping] = useState(canUseColorMapping);
 
   const { inputValue: localState, handleInputChange: setLocalState } = useDebouncedValue<XYState>({
     value: props.state,
@@ -79,6 +108,19 @@ export function DataDimensionEditor(
     [accessor, index, localState, layer, setLocalState]
   );
 
+  const setColorMapping = useCallback(
+    (colorMapping?: ColorMapping.Config) => {
+      setLocalState(updateLayer(localState, { ...layer, colorMapping }, index));
+    },
+    [index, localState, layer, setLocalState]
+  );
+  const setPalette = useCallback(
+    (palette: PaletteOutput) => {
+      setLocalState(updateLayer(localState, { ...layer, palette }, index));
+    },
+    [index, localState, layer, setLocalState]
+  );
+
   const overwriteColor = getSeriesColor(layer, accessor);
   const assignedColor = useMemo(() => {
     const sortedAccessors: string[] = getSortedAccessors(
@@ -105,19 +147,128 @@ export function DataDimensionEditor(
   }, [props.frame, props.paletteService, state.layers, accessor, props.formatFactory, layer]);
 
   const localLayer: XYDataLayerConfig = layer;
-  if (props.groupId === 'breakdown') {
+
+  const colors = layer.colorMapping
+    ? getColorsFromMapping(props.darkMode, layer.colorMapping)
+    : props.paletteService
+        .get(layer.palette?.name || 'default')
+        .getCategoricalColors(10, layer.palette);
+
+  const table = props.frame.activeData?.[layer.layerId];
+  const { splitAccessor } = layer;
+  const splitCategories = getColorCategories(table?.rows ?? [], splitAccessor);
+
+  if (props.groupId === 'breakdown' && !layer.collapseFn) {
     return (
-      <>
-        {!layer.collapseFn && (
-          <PalettePicker
-            palettes={props.paletteService}
-            activePalette={localLayer?.palette}
-            setPalette={(newPalette) => {
-              setState(updateLayer(localState, { ...localLayer, palette: newPalette }, index));
-            }}
-          />
-        )}
-      </>
+      <EuiFormRow
+        display="columnCompressed"
+        label={i18n.translate('xpack.lens.colorMapping.editColorMappingSectionlabel', {
+          defaultMessage: 'Color mapping',
+        })}
+        style={{ alignItems: 'center' }}
+      >
+        <EuiFlexGroup
+          alignItems="center"
+          gutterSize="s"
+          responsive={false}
+          className="lnsDynamicColoringClickable"
+        >
+          <EuiFlexItem>
+            <EuiColorPaletteDisplay
+              data-test-subj="lns_dynamicColoring_edit"
+              palette={colors}
+              type={'fixed'}
+              onClick={() => {
+                setIsPaletteOpen(!isPaletteOpen);
+              }}
+            />
+          </EuiFlexItem>
+          <EuiFlexItem grow={false}>
+            <EuiButtonIcon
+              data-test-subj="lns_colorEditing_trigger"
+              aria-label={i18n.translate('xpack.lens.colorMapping.editColorMappingButton', {
+                defaultMessage: 'Edit palette',
+              })}
+              iconType="controlsHorizontal"
+              onClick={() => {
+                setIsPaletteOpen(!isPaletteOpen);
+              }}
+              size="xs"
+            />
+            <PalettePanelContainer
+              siblingRef={props.panelRef}
+              isOpen={isPaletteOpen}
+              handleClose={() => setIsPaletteOpen(!isPaletteOpen)}
+              title={
+                useNewColorMapping
+                  ? i18n.translate('xpack.lens.colorMapping.editColorMappingTitle', {
+                      defaultMessage: 'Edit colors by term mapping',
+                    })
+                  : i18n.translate('xpack.lens.colorMapping.editColorsTitle', {
+                      defaultMessage: 'Edit colors',
+                    })
+              }
+            >
+              <div className="lnsPalettePanel__section lnsPalettePanel__section--shaded lnsIndexPatternDimensionEditor--padded">
+                <EuiFlexGroup direction="column" gutterSize="s" justifyContent="flexStart">
+                  <EuiFlexItem>
+                    <EuiSwitch
+                      label={
+                        <EuiText size="xs">
+                          <span>
+                            {i18n.translate('xpack.lens.colorMapping.tryLabel', {
+                              defaultMessage: 'Use the new Color Mapping feature',
+                            })}{' '}
+                            <EuiBadge color="hollow">
+                              {i18n.translate('xpack.lens.colorMapping.techPreviewLabel', {
+                                defaultMessage: 'Tech preview',
+                              })}
+                            </EuiBadge>
+                          </span>
+                        </EuiText>
+                      }
+                      data-test-subj="lns_colorMappingOrLegacyPalette_switch"
+                      compressed
+                      checked={useNewColorMapping}
+                      onChange={({ target: { checked } }) => {
+                        trackUiCounterEvents(
+                          `color_mapping_switch_${checked ? 'enabled' : 'disabled'}`
+                        );
+                        setColorMapping(checked ? { ...DEFAULT_COLOR_MAPPING_CONFIG } : undefined);
+                        setUseNewColorMapping(checked);
+                      }}
+                    />
+                    <EuiSpacer size="s" />
+                  </EuiFlexItem>
+                  <EuiFlexItem>
+                    {canUseColorMapping || useNewColorMapping ? (
+                      <CategoricalColorMapping
+                        isDarkMode={darkMode}
+                        model={layer.colorMapping ?? { ...DEFAULT_COLOR_MAPPING_CONFIG }}
+                        onModelUpdate={(model: ColorMapping.Config) => setColorMapping(model)}
+                        palettes={AVAILABLE_PALETTES}
+                        data={{
+                          type: 'categories',
+                          categories: splitCategories,
+                        }}
+                        specialTokens={SPECIAL_TOKENS_STRING_CONVERTION}
+                      />
+                    ) : (
+                      <PalettePicker
+                        palettes={props.paletteService}
+                        activePalette={localLayer?.palette}
+                        setPalette={(newPalette) => {
+                          setPalette(newPalette);
+                        }}
+                      />
+                    )}
+                  </EuiFlexItem>
+                </EuiFlexGroup>
+              </div>
+            </PalettePanelContainer>
+          </EuiFlexItem>
+        </EuiFlexGroup>
+      </EuiFormRow>
     );
   }
 
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/xy_config_panel.test.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/xy_config_panel.test.tsx
index 252c3de6b8e57..9a98f5bae168b 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/xy_config_panel.test.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/xy_config_panel.test.tsx
@@ -272,6 +272,7 @@ describe('XY Config panels', () => {
           addLayer={jest.fn()}
           removeLayer={jest.fn()}
           datasource={{} as DatasourcePublicAPI}
+          darkMode={false}
         />
       );
 
@@ -299,6 +300,7 @@ describe('XY Config panels', () => {
           addLayer={jest.fn()}
           removeLayer={jest.fn()}
           datasource={{} as DatasourcePublicAPI}
+          darkMode={false}
         />
       );
 
@@ -347,6 +349,7 @@ describe('XY Config panels', () => {
           addLayer={jest.fn()}
           removeLayer={jest.fn()}
           datasource={{} as DatasourcePublicAPI}
+          darkMode={false}
         />
       );
 
@@ -392,6 +395,7 @@ describe('XY Config panels', () => {
           addLayer={jest.fn()}
           removeLayer={jest.fn()}
           datasource={{} as DatasourcePublicAPI}
+          darkMode={false}
         />
       );
 
@@ -437,6 +441,7 @@ describe('XY Config panels', () => {
           addLayer={jest.fn()}
           removeLayer={jest.fn()}
           datasource={{} as DatasourcePublicAPI}
+          darkMode={false}
         />
       );
 
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts b/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts
index 8417d02d79995..a12afc7465579 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts
+++ b/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts
@@ -18,7 +18,7 @@ import { generateId } from '../../id_generator';
 import { getXyVisualization } from './xy_visualization';
 import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
 import { eventAnnotationServiceMock } from '@kbn/event-annotation-plugin/public/mocks';
-import type { PaletteOutput } from '@kbn/coloring';
+import { DEFAULT_COLOR_MAPPING_CONFIG, PaletteOutput } from '@kbn/coloring';
 import { LayerTypes } from '@kbn/expression-xy-plugin/public';
 import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks';
 import { coreMock, themeServiceMock } from '@kbn/core/public/mocks';
@@ -757,7 +757,7 @@ describe('xy_suggestions', () => {
         changeType: 'unchanged',
       },
       keptLayerIds: [],
-      mainPalette,
+      mainPalette: { type: 'legacyPalette', value: mainPalette },
     });
 
     expect((suggestion.state.layers as XYDataLayerConfig[])[0].palette).toEqual(mainPalette);
@@ -773,7 +773,7 @@ describe('xy_suggestions', () => {
         changeType: 'unchanged',
       },
       keptLayerIds: [],
-      mainPalette,
+      mainPalette: { type: 'legacyPalette', value: mainPalette },
     });
 
     expect((suggestion.state.layers as XYDataLayerConfig[])[0].palette).toEqual(undefined);
@@ -913,7 +913,13 @@ describe('xy_suggestions', () => {
     expect(suggestions[0].state).toEqual({
       ...currentState,
       preferredSeriesType: 'line',
-      layers: [{ ...currentState.layers[0], seriesType: 'line' }],
+      layers: [
+        {
+          ...currentState.layers[0],
+          seriesType: 'line',
+          colorMapping: DEFAULT_COLOR_MAPPING_CONFIG,
+        },
+      ],
     });
     expect(suggestions[0].title).toEqual('Line chart');
   });
@@ -954,12 +960,24 @@ describe('xy_suggestions', () => {
     expect(seriesSuggestion.state).toEqual({
       ...currentState,
       preferredSeriesType: 'line',
-      layers: [{ ...currentState.layers[0], seriesType: 'line' }],
+      layers: [
+        {
+          ...currentState.layers[0],
+          seriesType: 'line',
+          colorMapping: DEFAULT_COLOR_MAPPING_CONFIG,
+        },
+      ],
     });
     expect(stackSuggestion.state).toEqual({
       ...currentState,
       preferredSeriesType: 'bar_stacked',
-      layers: [{ ...currentState.layers[0], seriesType: 'bar_stacked' }],
+      layers: [
+        {
+          ...currentState.layers[0],
+          seriesType: 'bar_stacked',
+          colorMapping: DEFAULT_COLOR_MAPPING_CONFIG,
+        },
+      ],
     });
     expect(seriesSuggestion.title).toEqual('Line chart');
     expect(stackSuggestion.title).toEqual('Stacked');
@@ -1081,6 +1099,7 @@ describe('xy_suggestions', () => {
           ...currentState.layers[0],
           xAccessor: 'product',
           splitAccessor: 'category',
+          colorMapping: DEFAULT_COLOR_MAPPING_CONFIG,
         },
       ],
     });
@@ -1126,6 +1145,7 @@ describe('xy_suggestions', () => {
           ...currentState.layers[0],
           xAccessor: 'category',
           splitAccessor: 'product',
+          colorMapping: DEFAULT_COLOR_MAPPING_CONFIG,
         },
       ],
     });
@@ -1172,6 +1192,7 @@ describe('xy_suggestions', () => {
           ...currentState.layers[0],
           xAccessor: 'timestamp',
           splitAccessor: 'product',
+          colorMapping: DEFAULT_COLOR_MAPPING_CONFIG,
         },
       ],
     });
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.ts b/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.ts
index 33381822b6eed..b63acd9513300 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.ts
+++ b/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.ts
@@ -8,8 +8,8 @@
 import { i18n } from '@kbn/i18n';
 import { partition } from 'lodash';
 import { Position } from '@elastic/charts';
-import type { PaletteOutput } from '@kbn/coloring';
 import { LayerTypes } from '@kbn/expression-xy-plugin/public';
+import { DEFAULT_COLOR_MAPPING_CONFIG } from '@kbn/coloring';
 import type {
   SuggestionRequest,
   VisualizationSuggestion,
@@ -96,7 +96,7 @@ function getSuggestionForColumns(
   keptLayerIds: string[],
   currentState?: State,
   seriesType?: SeriesType,
-  mainPalette?: PaletteOutput,
+  mainPalette?: SuggestionRequest['mainPalette'],
   allowMixed?: boolean
 ): VisualizationSuggestion<State> | Array<VisualizationSuggestion<State>> | undefined {
   const [buckets, values] = partition(table.columns, (col) => col.operation.isBucketed);
@@ -230,7 +230,7 @@ function getSuggestionsForLayer({
   tableLabel?: string;
   keptLayerIds: string[];
   requestedSeriesType?: SeriesType;
-  mainPalette?: PaletteOutput;
+  mainPalette?: SuggestionRequest['mainPalette'];
   allowMixed?: boolean;
 }): VisualizationSuggestion<State> | Array<VisualizationSuggestion<State>> {
   const title = getSuggestionTitle(yValues, xValue, tableLabel);
@@ -493,7 +493,7 @@ function buildSuggestion({
   changeType: TableChangeType;
   keptLayerIds: string[];
   hide?: boolean;
-  mainPalette?: PaletteOutput;
+  mainPalette?: SuggestionRequest['mainPalette'];
   allowMixed?: boolean;
 }) {
   if (seriesType.includes('percentage') && xValue?.operation.scale === 'ordinal' && !splitBy) {
@@ -505,10 +505,11 @@ function buildSuggestion({
   const newLayer: XYDataLayerConfig = {
     ...(existingLayer || {}),
     palette:
-      mainPalette ||
-      (existingLayer && 'palette' in existingLayer
+      mainPalette?.type === 'legacyPalette'
+        ? mainPalette.value
+        : existingLayer && 'palette' in existingLayer
         ? (existingLayer as XYDataLayerConfig).palette
-        : undefined),
+        : undefined,
     layerId,
     seriesType,
     xAccessor: xValue?.columnId,
@@ -519,6 +520,11 @@ function buildSuggestion({
         ? existingLayer.yConfig.filter(({ forAccessor }) => accessors.indexOf(forAccessor) !== -1)
         : undefined,
     layerType: LayerTypes.DATA,
+    colorMapping: !mainPalette
+      ? { ...DEFAULT_COLOR_MAPPING_CONFIG }
+      : mainPalette?.type === 'colorMapping'
+      ? mainPalette.value
+      : undefined,
   };
 
   const hasDateHistogramDomain =
diff --git a/x-pack/plugins/lens/tsconfig.json b/x-pack/plugins/lens/tsconfig.json
index ca536dc187c3f..4fb8f849d6d27 100644
--- a/x-pack/plugins/lens/tsconfig.json
+++ b/x-pack/plugins/lens/tsconfig.json
@@ -86,6 +86,7 @@
     "@kbn/content-management-utils",
     "@kbn/serverless",
     "@kbn/ebt-tools",
+    "@kbn/chart-expressions-common",
     "@kbn/search-response-warnings",
   ],
   "exclude": [
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index 4ae31ffb358ef..8981de9b982c7 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -21322,7 +21322,6 @@
     "xpack.lens.table.dynamicColoring.text": "Texte",
     "xpack.lens.table.hide.hideLabel": "Masquer",
     "xpack.lens.table.palettePanelContainer.back": "Retour",
-    "xpack.lens.table.palettePanelTitle": "Couleur",
     "xpack.lens.table.resize.reset": "Réinitialiser la largeur",
     "xpack.lens.table.rowHeight.auto": "Ajustement automatique",
     "xpack.lens.table.rowHeight.custom": "Personnalisé",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 87e23ff29cb1f..41665e626fc8c 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -21337,7 +21337,6 @@
     "xpack.lens.table.dynamicColoring.text": "テキスト",
     "xpack.lens.table.hide.hideLabel": "非表示",
     "xpack.lens.table.palettePanelContainer.back": "戻る",
-    "xpack.lens.table.palettePanelTitle": "色",
     "xpack.lens.table.resize.reset": "幅のリセット",
     "xpack.lens.table.rowHeight.auto": "自動的に合わせる",
     "xpack.lens.table.rowHeight.custom": "カスタム",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 6e3c128ec642a..33625a62a6356 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -21337,7 +21337,6 @@
     "xpack.lens.table.dynamicColoring.text": "文本",
     "xpack.lens.table.hide.hideLabel": "隐藏",
     "xpack.lens.table.palettePanelContainer.back": "返回",
-    "xpack.lens.table.palettePanelTitle": "颜色",
     "xpack.lens.table.resize.reset": "重置宽度",
     "xpack.lens.table.rowHeight.auto": "自动适应",
     "xpack.lens.table.rowHeight.custom": "定制",
diff --git a/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts b/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts
index 5d1f590490c4d..a388a4d90af3b 100644
--- a/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts
+++ b/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts
@@ -68,6 +68,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
         dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension',
         operation: 'terms',
         field: 'geo.src',
+        palette: { mode: 'legacy', id: 'default' },
       });
 
       await PageObjects.lens.save('vis1', false, true);
@@ -84,6 +85,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
         dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension',
         operation: 'terms',
         field: 'geo.src',
+        palette: { mode: 'legacy', id: 'default' },
       });
 
       await filterBar.addFilter({ field: 'geo.src', operation: 'is not', value: 'CN' });
diff --git a/x-pack/test/functional/apps/lens/group4/color_mapping.ts b/x-pack/test/functional/apps/lens/group4/color_mapping.ts
new file mode 100644
index 0000000000000..0fc9f79afec79
--- /dev/null
+++ b/x-pack/test/functional/apps/lens/group4/color_mapping.ts
@@ -0,0 +1,73 @@
+/*
+ * Copyright 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 expect from '@kbn/expect';
+
+import {
+  EUI_AMSTERDAM_PALETTE_COLORS,
+  ELASTIC_BRAND_PALETTE_COLORS,
+  EUIAmsterdamColorBlindPalette,
+  ElasticBrandPalette,
+} from '@kbn/coloring/src/shared_components/color_mapping/palettes';
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+  const PageObjects = getPageObjects(['visualize', 'lens', 'common']);
+  const elasticChart = getService('elasticChart');
+
+  describe('lens color mapping', () => {
+    before(async () => {
+      await PageObjects.visualize.navigateToNewVisualization();
+      await PageObjects.visualize.clickVisType('lens');
+      await PageObjects.lens.goToTimeRange();
+      await elasticChart.setNewChartUiDebugFlag(true);
+
+      await PageObjects.lens.configureDimension({
+        dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension',
+        operation: 'average',
+        field: 'bytes',
+      });
+
+      await PageObjects.lens.configureDimension({
+        dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension',
+        operation: 'terms',
+        field: 'extension.raw',
+        palette: { mode: 'colorMapping', id: ElasticBrandPalette.id },
+        keepOpen: true,
+      });
+    });
+
+    it('should render correct color mapping', async () => {
+      const chart = await PageObjects.lens.getCurrentChartDebugState('xyVisChart');
+      const legendColors = chart?.legend?.items?.map((item) => item.color.toLowerCase()) ?? [];
+      expect(legendColors).to.eql(
+        ELASTIC_BRAND_PALETTE_COLORS.slice(0, 5).map((c) => c.toLowerCase())
+      );
+    });
+    it('should allow switching color mapping palette', async () => {
+      await PageObjects.lens.changeColorMappingPalette(
+        'lnsXY_splitDimensionPanel > lnsLayerPanel-dimensionLink',
+        EUIAmsterdamColorBlindPalette.id
+      );
+      const chart = await PageObjects.lens.getCurrentChartDebugState('xyVisChart');
+      const legendColors = chart?.legend?.items?.map((item) => item.color.toLowerCase()) ?? [];
+      expect(legendColors).to.eql(
+        EUI_AMSTERDAM_PALETTE_COLORS.slice(0, 5).map((c) => c.toLowerCase())
+      );
+    });
+
+    it('should change categorical color', async () => {
+      await PageObjects.lens.changeColorMappingCategoricalColors(
+        'lnsXY_splitDimensionPanel > lnsLayerPanel-dimensionLink',
+        0,
+        3
+      );
+      const chart = await PageObjects.lens.getCurrentChartDebugState('xyVisChart');
+      const firstLegendItemColor = chart?.legend?.items?.[0]?.color?.toLowerCase() ?? 'NONE';
+      expect(firstLegendItemColor).to.eql(EUI_AMSTERDAM_PALETTE_COLORS[3].toLowerCase());
+    });
+  });
+}
diff --git a/x-pack/test/functional/apps/lens/group4/colors.ts b/x-pack/test/functional/apps/lens/group4/colors.ts
index 4078b0663d7ae..20265d247cf83 100644
--- a/x-pack/test/functional/apps/lens/group4/colors.ts
+++ b/x-pack/test/functional/apps/lens/group4/colors.ts
@@ -4,13 +4,14 @@
  * 2.0; you may not use this file except in compliance with the Elastic License
  * 2.0.
  */
+import { ElasticBrandPalette } from '@kbn/coloring';
 import { FtrProviderContext } from '../../../ftr_provider_context';
 
 export default function ({ getService, getPageObjects }: FtrProviderContext) {
   const PageObjects = getPageObjects(['visualize', 'lens', 'common']);
 
   describe('lens color palette tests', () => {
-    it('should allow to pick color palette in xy chart', async () => {
+    it('should allow to pick legacy color palette in xy chart', async () => {
       await PageObjects.visualize.navigateToNewVisualization();
       await PageObjects.visualize.clickVisType('lens');
       await PageObjects.lens.goToTimeRange();
@@ -31,11 +32,38 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
         dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension',
         operation: 'terms',
         field: '@message.raw',
-        palette: 'negative',
+        palette: { mode: 'legacy', id: 'negative' },
         keepOpen: true,
       });
 
-      await PageObjects.lens.assertPalette('negative');
+      await PageObjects.lens.assertPalette('negative', true);
+    });
+    it('should allow to pick color mapping palette in xy chart', async () => {
+      await PageObjects.visualize.navigateToNewVisualization();
+      await PageObjects.visualize.clickVisType('lens');
+      await PageObjects.lens.goToTimeRange();
+
+      await PageObjects.lens.configureDimension({
+        dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension',
+        operation: 'terms',
+        field: 'geo.src',
+      });
+
+      await PageObjects.lens.configureDimension({
+        dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension',
+        operation: 'average',
+        field: 'bytes',
+      });
+
+      await PageObjects.lens.configureDimension({
+        dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension',
+        operation: 'terms',
+        field: '@message.raw',
+        palette: { mode: 'colorMapping', id: ElasticBrandPalette.id },
+        keepOpen: true,
+      });
+
+      await PageObjects.lens.assertPalette(ElasticBrandPalette.id, false);
     });
 
     it('should carry over palette to the pie chart', async () => {
@@ -43,7 +71,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
       await PageObjects.lens.openDimensionEditor(
         'lnsPie_sliceByDimensionPanel > lns-dimensionTrigger'
       );
-      await PageObjects.lens.assertPalette('negative');
+      await PageObjects.lens.assertPalette(ElasticBrandPalette.id, false);
     });
 
     it('should carry palette back to the bar chart', async () => {
@@ -51,7 +79,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
       await PageObjects.lens.openDimensionEditor(
         'lnsXY_splitDimensionPanel > lns-dimensionTrigger'
       );
-      await PageObjects.lens.assertPalette('negative');
+      await PageObjects.lens.assertPalette(ElasticBrandPalette.id, false);
     });
   });
 }
diff --git a/x-pack/test/functional/apps/lens/group4/index.ts b/x-pack/test/functional/apps/lens/group4/index.ts
index 13cfa3fe421e1..9627b744300ba 100644
--- a/x-pack/test/functional/apps/lens/group4/index.ts
+++ b/x-pack/test/functional/apps/lens/group4/index.ts
@@ -73,6 +73,7 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext
 
     // total run time ~16m 30s
     loadTestFile(require.resolve('./colors')); // 1m 2s
+    loadTestFile(require.resolve('./color_mapping'));
     loadTestFile(require.resolve('./chart_data')); // 1m 10s
     loadTestFile(require.resolve('./time_shift')); // 1m
     loadTestFile(require.resolve('./dashboard')); // 6m 45s
diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts
index 493977e7fc735..6069ae838cea8 100644
--- a/x-pack/test/functional/page_objects/lens_page.ts
+++ b/x-pack/test/functional/page_objects/lens_page.ts
@@ -178,7 +178,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
       field?: string;
       isPreviousIncompatible?: boolean;
       keepOpen?: boolean;
-      palette?: string;
+      palette?: { mode: 'legacy' | 'colorMapping'; id: string };
       formula?: string;
       disableEmptyRows?: boolean;
     }) {
@@ -205,7 +205,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
       }
 
       if (opts.palette) {
-        await this.setPalette(opts.palette);
+        await this.setPalette(opts.palette.id, opts.palette.mode === 'legacy');
       }
 
       if (opts.disableEmptyRows) {
@@ -519,13 +519,26 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
       await PageObjects.header.waitUntilLoadingHasFinished();
     },
 
-    async assertPalette(palette: string) {
+    async assertPalette(paletteId: string, isLegacy: boolean) {
       await retry.try(async () => {
-        await testSubjects.click('lns-palettePicker');
+        await testSubjects.click('lns_colorEditing_trigger');
+        // open the palette picker
+        if (isLegacy) {
+          await testSubjects.click('lns-palettePicker');
+        } else {
+          await testSubjects.click('kbnColoring_ColorMapping_PalettePicker');
+        }
         const currentPalette = await (
           await find.byCssSelector('[role=option][aria-selected=true]')
         ).getAttribute('id');
-        expect(currentPalette).to.equal(palette);
+        // close the palette picker
+        if (isLegacy) {
+          await testSubjects.click('lns-palettePicker');
+        } else {
+          await testSubjects.click('kbnColoring_ColorMapping_PalettePicker');
+        }
+        expect(currentPalette).to.equal(paletteId);
+        await this.closePaletteEditor();
       });
     },
 
@@ -1214,9 +1227,20 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
       await testSubjects.click(`${paletteName}-palette`);
     },
 
-    async setPalette(paletteName: string) {
-      await testSubjects.click('lns-palettePicker');
-      await find.clickByCssSelector(`#${paletteName}`);
+    async setPalette(paletteId: string, isLegacy: boolean) {
+      await testSubjects.click('lns_colorEditing_trigger');
+      await testSubjects.setEuiSwitch(
+        'lns_colorMappingOrLegacyPalette_switch',
+        isLegacy ? 'uncheck' : 'check'
+      );
+      if (isLegacy) {
+        await testSubjects.click('lns-palettePicker');
+        await find.clickByCssSelector(`#${paletteId}`);
+      } else {
+        await testSubjects.click('kbnColoring_ColorMapping_PalettePicker');
+        await testSubjects.click(`kbnColoring_ColorMapping_Palette-${paletteId}`);
+      }
+      await this.closePaletteEditor();
     },
 
     async closePaletteEditor() {
@@ -1456,7 +1480,6 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
       dimension: string;
       field: string;
       keepOpen?: boolean;
-      palette?: string;
     }) {
       await retry.try(async () => {
         if (!(await testSubjects.exists('lns-indexPattern-dimensionContainerClose'))) {
@@ -1467,10 +1490,6 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
 
       await this.selectOptionFromComboBox('text-based-dimension-field', opts.field);
 
-      if (opts.palette) {
-        await this.setPalette(opts.palette);
-      }
-
       if (!opts.keepOpen) {
         await this.closeDimensionEditor();
         await testSubjects.click('applyFlyoutButton');
@@ -1857,5 +1876,42 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
         await testSubjects.click('lensSuggestionsPanelToggleButton');
       }
     },
+
+    async changeColorMappingPalette(selector: string, paletteId: string) {
+      await retry.try(async () => {
+        if (!(await testSubjects.exists('lns-indexPattern-dimensionContainerClose'))) {
+          await testSubjects.click(selector);
+        }
+        await testSubjects.existOrFail('lns-indexPattern-dimensionContainerClose');
+      });
+      await this.setPalette(paletteId, false);
+      await this.closeDimensionEditor();
+    },
+
+    async changeColorMappingCategoricalColors(
+      selector: string,
+      colorSwatchIndex: number,
+      paletteColorIndex: number
+    ) {
+      await retry.try(async () => {
+        if (!(await testSubjects.exists('lns-indexPattern-dimensionContainerClose'))) {
+          await testSubjects.click(selector);
+        }
+        await testSubjects.existOrFail('lns-indexPattern-dimensionContainerClose');
+      });
+      await testSubjects.click('lns_colorEditing_trigger');
+      // disable autoAssign
+      await testSubjects.setEuiSwitch('lns-colorMapping-autoAssignSwitch', 'uncheck');
+
+      await testSubjects.click(`lns-colorMapping-colorSwatch-${colorSwatchIndex}`);
+
+      await testSubjects.click(`lns-colorMapping-colorPicker-staticColor-${paletteColorIndex}`);
+
+      await testSubjects.click(`lns-colorMapping-colorSwatch-${colorSwatchIndex}`);
+
+      await this.closePaletteEditor();
+
+      await this.closeDimensionEditor();
+    },
   });
 }
diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json
index 93d4866ef0933..bf7d721fc0ba2 100644
--- a/x-pack/test/tsconfig.json
+++ b/x-pack/test/tsconfig.json
@@ -141,6 +141,7 @@
     "@kbn/aiops-utils",
     "@kbn/stack-alerts-plugin",
     "@kbn/apm-data-access-plugin",
+    "@kbn/coloring",
     "@kbn/profiling-utils",
     "@kbn/profiling-data-access-plugin",
   ]

From 428f3e05ec47429948ca59b772853bf1e37e25ab Mon Sep 17 00:00:00 2001
From: Ash <1849116+ashokaditya@users.noreply.github.com>
Date: Thu, 28 Sep 2023 14:18:05 +0200
Subject: [PATCH 13/20] [Security Solution][Endpoint] Unskip metadata API ftr
 test (#167226)

## Summary

Adds an error handler block to help debug test setup errors during fleet
agent setup for endpoint API ftr tests. Also adds missing header API
version.

fixes elastic/kibana/issues/151854

**flaky ftr test runners**
-
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3223
x 150 (without header) - (1 fail)
-
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3224
x 50 (all 50 pass)
-
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3228
x 100 (2 fails)
-
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3231
x 100 ( 1 fail)
-
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3241
x 50 ( 2 fails)
### Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
 x-pack/test/common/services/ingest_manager.ts |  4 ++-
 .../page_objects/infra_hosts_view.ts          |  1 +
 .../apis/data_stream_helper.ts                | 20 +++++++-------
 .../apis/index.ts                             |  8 ++++--
 .../apis/metadata.ts                          | 26 +++++++++++--------
 5 files changed, 34 insertions(+), 25 deletions(-)

diff --git a/x-pack/test/common/services/ingest_manager.ts b/x-pack/test/common/services/ingest_manager.ts
index 1ef80be40d803..c3187213d905d 100644
--- a/x-pack/test/common/services/ingest_manager.ts
+++ b/x-pack/test/common/services/ingest_manager.ts
@@ -5,7 +5,8 @@
  * 2.0.
  */
 
-import { fleetSetupRouteService } from '@kbn/fleet-plugin/common';
+import { API_VERSIONS, fleetSetupRouteService } from '@kbn/fleet-plugin/common';
+import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
 import { FtrProviderContext } from '../ftr_provider_context';
 
 export function IngestManagerProvider({ getService }: FtrProviderContext) {
@@ -18,6 +19,7 @@ export function IngestManagerProvider({ getService }: FtrProviderContext) {
       await retry.try(async () => {
         await supertest
           .post(fleetSetupRouteService.postFleetSetupPath())
+          .set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.public.v1)
           .set(headers)
           .send({ forceRecreate: true })
           .expect(200);
diff --git a/x-pack/test/functional/page_objects/infra_hosts_view.ts b/x-pack/test/functional/page_objects/infra_hosts_view.ts
index 4dda165ea96a7..83628e1eae02a 100644
--- a/x-pack/test/functional/page_objects/infra_hosts_view.ts
+++ b/x-pack/test/functional/page_objects/infra_hosts_view.ts
@@ -4,6 +4,7 @@
  * 2.0; you may not use this file except in compliance with the Elastic License
  * 2.0.
  */
+
 import { AlertStatus } from '@kbn/rule-data-utils';
 import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
 import { FtrProviderContext } from '../ftr_provider_context';
diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/data_stream_helper.ts b/x-pack/test/security_solution_endpoint_api_int/apis/data_stream_helper.ts
index 1caef61bec80c..67115c1610157 100644
--- a/x-pack/test/security_solution_endpoint_api_int/apis/data_stream_helper.ts
+++ b/x-pack/test/security_solution_endpoint_api_int/apis/data_stream_helper.ts
@@ -8,14 +8,14 @@
 import { Client } from '@elastic/elasticsearch';
 import { AGENTS_INDEX } from '@kbn/fleet-plugin/common';
 import {
-  metadataIndexPattern,
-  eventsIndexPattern,
   alertsIndexPattern,
-  policyIndexPattern,
+  eventsIndexPattern,
+  METADATA_DATASTREAM,
+  METADATA_UNITED_INDEX,
   metadataCurrentIndexPattern,
+  metadataIndexPattern,
+  policyIndexPattern,
   telemetryIndexPattern,
-  METADATA_UNITED_INDEX,
-  METADATA_DATASTREAM,
 } from '@kbn/security-solution-plugin/common/endpoint/constants';
 
 export function deleteDataStream(getService: (serviceName: 'es') => Client, index: string) {
@@ -38,10 +38,8 @@ export async function deleteAllDocsFromIndex(
   const client = getService('es');
   await client.deleteByQuery(
     {
-      body: {
-        query: {
-          match_all: {},
-        },
+      query: {
+        match_all: {},
       },
       index,
       wait_for_completion: true,
@@ -132,12 +130,12 @@ export function bulkIndex(
   index: string,
   docs: unknown[]
 ) {
-  const body = docs.flatMap((doc) => [{ create: { _index: index } }, doc]);
+  const operations = docs.flatMap((doc) => [{ create: { _index: index } }, doc]);
   const client = getService('es');
 
   return client.bulk({
     index,
     refresh: 'wait_for',
-    body,
+    operations,
   });
 }
diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts
index e68db8182da20..06d54f0aaac45 100644
--- a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts
+++ b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts
@@ -7,7 +7,7 @@
 
 import { getRegistryUrl as getRegistryUrlFromIngest } from '@kbn/fleet-plugin/server';
 import { FtrProviderContext } from '../ftr_provider_context';
-import { isRegistryEnabled, getRegistryUrlFromTestEnv } from '../registry';
+import { getRegistryUrlFromTestEnv, isRegistryEnabled } from '../registry';
 import { ROLE } from '../services/roles_users';
 
 export default function endpointAPIIntegrationTests(providerContext: FtrProviderContext) {
@@ -28,7 +28,11 @@ export default function endpointAPIIntegrationTests(providerContext: FtrProvider
 
     const roles = Object.values(ROLE);
     before(async () => {
-      await ingestManager.setup();
+      try {
+        await ingestManager.setup();
+      } catch (err) {
+        log.warning(`Error setting up ingestManager: ${err}`);
+      }
 
       // create role/user
       for (const role of roles) {
diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts
index c4778f7039a91..a015cf693895e 100644
--- a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts
+++ b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts
@@ -9,14 +9,14 @@ import { v4 as uuidv4 } from 'uuid';
 import expect from '@kbn/expect';
 import { TransformGetTransformStatsTransformStats } from '@elastic/elasticsearch/lib/api/types';
 import {
-  METADATA_DATASTREAM,
+  ENDPOINT_DEFAULT_SORT_DIRECTION,
+  ENDPOINT_DEFAULT_SORT_FIELD,
   HOST_METADATA_LIST_ROUTE,
+  METADATA_DATASTREAM,
+  METADATA_TRANSFORMS_STATUS_ROUTE,
   METADATA_UNITED_INDEX,
   METADATA_UNITED_TRANSFORM,
-  METADATA_TRANSFORMS_STATUS_ROUTE,
   metadataTransformPrefix,
-  ENDPOINT_DEFAULT_SORT_FIELD,
-  ENDPOINT_DEFAULT_SORT_DIRECTION,
 } from '@kbn/security-solution-plugin/common/endpoint/constants';
 import { AGENTS_INDEX } from '@kbn/fleet-plugin/common';
 import { indexFleetEndpointPolicy } from '@kbn/security-solution-plugin/common/endpoint/data_loaders/index_fleet_endpoint_policy';
@@ -29,22 +29,22 @@ import {
 } from '@kbn/security-solution-plugin/common/endpoint/types';
 import { generateAgentDocs, generateMetadataDocs } from './metadata.fixtures';
 import {
+  bulkIndex,
+  deleteAllDocsFromFleetAgents,
+  deleteAllDocsFromIndex,
   deleteAllDocsFromMetadataCurrentIndex,
   deleteAllDocsFromMetadataDatastream,
-  stopTransform,
   startTransform,
-  deleteAllDocsFromFleetAgents,
-  deleteAllDocsFromIndex,
-  bulkIndex,
+  stopTransform,
 } from './data_stream_helper';
 import { FtrProviderContext } from '../ftr_provider_context';
 
 export default function ({ getService }: FtrProviderContext) {
   const supertest = getService('supertest');
   const endpointTestResources = getService('endpointTestResources');
+  const log = getService('log');
 
-  // Failing: See https://github.com/elastic/kibana/issues/151854
-  describe.skip('test metadata apis', () => {
+  describe('test metadata apis', () => {
     describe('list endpoints GET route', () => {
       const numberOfHostsInFixture = 2;
       let agent1Timestamp: number;
@@ -55,7 +55,11 @@ export default function ({ getService }: FtrProviderContext) {
         await deleteAllDocsFromFleetAgents(getService);
         await deleteAllDocsFromMetadataDatastream(getService);
         await deleteAllDocsFromMetadataCurrentIndex(getService);
-        await deleteAllDocsFromIndex(getService, METADATA_UNITED_INDEX);
+        try {
+          await deleteAllDocsFromIndex(getService, METADATA_UNITED_INDEX);
+        } catch (err) {
+          log.warning(`Unable to delete index: ${err}`);
+        }
 
         const customIndexFn = async (): Promise<IndexedHostsAndAlertsResponse> => {
           // generate an endpoint policy and attach id to agents since

From 239e50389f206a0df9fc311ca5760bd8c1a5236e Mon Sep 17 00:00:00 2001
From: Rudolf Meijering <skaapgif@gmail.com>
Date: Thu, 28 Sep 2023 14:27:14 +0200
Subject: [PATCH 14/20] Revert "[inspector] show request method, path, and
 querystring" (#167485)

This reverts commit d61a5a051648facb8e6dfbd022b78b0d3ed3c311 from
#166565

## Summary

Summarize your PR. If it involves visual changes include a screenshot or
gif.


### Checklist

Delete any items that are not applicable to this PR.

- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)


### Risk Matrix

Delete this section if it is not applicable to this PR.

Before closing this PR, invite QA, stakeholders, and other developers to
identify risks that should be tested prior to the change/feature
release.

When forming the risk matrix, consider some of the following examples
and how they may potentially impact the change:

| Risk | Probability | Severity | Mitigation/Notes |

|---------------------------|-------------|----------|-------------------------|
| Multiple Spaces&mdash;unexpected behavior in non-default Kibana Space.
| Low | High | Integration tests will verify that all features are still
supported in non-default Kibana Space and when user switches between
spaces. |
| Multiple nodes&mdash;Elasticsearch polling might have race conditions
when multiple Kibana nodes are polling for the same tasks. | High | Low
| Tasks are idempotent, so executing them multiple times will not result
in logical error, but will degrade performance. To test for this case we
add plenty of unit tests around this logic and document manual testing
procedure. |
| Code should gracefully handle cases when feature X or plugin Y are
disabled. | Medium | High | Unit tests will verify that any feature flag
or plugin combination still results in our service operational. |
| [See more potential risk
examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) |


### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
---
 .../data/common/search/expressions/esdsl.ts   |  16 +-
 .../data/common/search/expressions/esql.ts    |   8 +-
 .../data/common/search/expressions/essql.ts   |  10 +-
 src/plugins/data/common/search/types.ts       |   6 -
 .../search_interceptor/search_interceptor.ts  |  21 --
 .../data/server/search/routes/bsearch.ts      |   7 -
 .../eql_search/eql_search_strategy.ts         |   5 +-
 .../strategies/eql_search/response_utils.ts   |   5 +-
 .../es_search/es_search_strategy.test.ts      |   7 +-
 .../es_search/es_search_strategy.ts           |   5 +-
 .../strategies/es_search/response_utils.ts    |   7 +-
 .../ese_search/ese_search_strategy.ts         |  10 +-
 .../strategies/ese_search/response_utils.ts   |   8 +-
 .../esql_search/esql_search_strategy.ts       |   4 +-
 .../strategies/sql_search/response_utils.ts   |   5 +-
 .../sql_search/sql_search_strategy.ts         |   8 +-
 .../move_request_params_to_top_level.test.ts  |  66 ----
 .../move_request_params_to_top_level.ts       |  56 ---
 .../adapters/request/request_responder.ts     |   3 +-
 .../common/adapters/request/types.ts          |   4 -
 .../components/details/req_code_viewer.tsx    |  28 +-
 .../details/req_details_request.tsx           |   1 -
 .../server/report_server_error.ts             |  14 +-
 test/api_integration/apis/search/bsearch.ts   | 345 ------------------
 .../apps/visualize/group2/_inspector.ts       |   8 +-
 test/functional/services/inspector.ts         |  15 -
 26 files changed, 45 insertions(+), 627 deletions(-)
 delete mode 100644 src/plugins/inspector/common/adapters/request/move_request_params_to_top_level.test.ts
 delete mode 100644 src/plugins/inspector/common/adapters/request/move_request_params_to_top_level.ts

diff --git a/src/plugins/data/common/search/expressions/esdsl.ts b/src/plugins/data/common/search/expressions/esdsl.ts
index cc0a84cd4a908..34a67223b4be5 100644
--- a/src/plugins/data/common/search/expressions/esdsl.ts
+++ b/src/plugins/data/common/search/expressions/esdsl.ts
@@ -126,7 +126,7 @@ export const getEsdslFn = ({
       });
 
       try {
-        const finalResponse = await lastValueFrom(
+        const { rawResponse } = await lastValueFrom(
           search(
             {
               params: {
@@ -141,14 +141,14 @@ export const getEsdslFn = ({
 
         const stats: RequestStatistics = {};
 
-        if (finalResponse.rawResponse?.took) {
+        if (rawResponse?.took) {
           stats.queryTime = {
             label: i18n.translate('data.search.es_search.queryTimeLabel', {
               defaultMessage: 'Query time',
             }),
             value: i18n.translate('data.search.es_search.queryTimeValue', {
               defaultMessage: '{queryTime}ms',
-              values: { queryTime: finalResponse.rawResponse.took },
+              values: { queryTime: rawResponse.took },
             }),
             description: i18n.translate('data.search.es_search.queryTimeDescription', {
               defaultMessage:
@@ -158,12 +158,12 @@ export const getEsdslFn = ({
           };
         }
 
-        if (finalResponse.rawResponse?.hits) {
+        if (rawResponse?.hits) {
           stats.hitsTotal = {
             label: i18n.translate('data.search.es_search.hitsTotalLabel', {
               defaultMessage: 'Hits (total)',
             }),
-            value: `${finalResponse.rawResponse.hits.total}`,
+            value: `${rawResponse.hits.total}`,
             description: i18n.translate('data.search.es_search.hitsTotalDescription', {
               defaultMessage: 'The number of documents that match the query.',
             }),
@@ -173,19 +173,19 @@ export const getEsdslFn = ({
             label: i18n.translate('data.search.es_search.hitsLabel', {
               defaultMessage: 'Hits',
             }),
-            value: `${finalResponse.rawResponse.hits.hits.length}`,
+            value: `${rawResponse.hits.hits.length}`,
             description: i18n.translate('data.search.es_search.hitsDescription', {
               defaultMessage: 'The number of documents returned by the query.',
             }),
           };
         }
 
-        request.stats(stats).ok({ json: finalResponse });
+        request.stats(stats).ok({ json: rawResponse });
         request.json(dsl);
 
         return {
           type: 'es_raw_response',
-          body: finalResponse.rawResponse,
+          body: rawResponse,
         };
       } catch (e) {
         request.error({ json: e });
diff --git a/src/plugins/data/common/search/expressions/esql.ts b/src/plugins/data/common/search/expressions/esql.ts
index b2d6a0458c63b..8ef0f49588303 100644
--- a/src/plugins/data/common/search/expressions/esql.ts
+++ b/src/plugins/data/common/search/expressions/esql.ts
@@ -210,24 +210,24 @@ export const getEsqlFn = ({ getStartDependencies }: EsqlFnArguments) => {
               return throwError(() => error);
             }),
             tap({
-              next(finalResponse) {
+              next({ rawResponse }) {
                 logInspectorRequest()
                   .stats({
                     hits: {
                       label: i18n.translate('data.search.es_search.hitsLabel', {
                         defaultMessage: 'Hits',
                       }),
-                      value: `${finalResponse.rawResponse.values.length}`,
+                      value: `${rawResponse.values.length}`,
                       description: i18n.translate('data.search.es_search.hitsDescription', {
                         defaultMessage: 'The number of documents returned by the query.',
                       }),
                     },
                   })
                   .json(params)
-                  .ok({ json: finalResponse });
+                  .ok({ json: rawResponse });
               },
               error(error) {
-                logInspectorRequest().json(params).error({ json: error });
+                logInspectorRequest().error({ json: error });
               },
             })
           );
diff --git a/src/plugins/data/common/search/expressions/essql.ts b/src/plugins/data/common/search/expressions/essql.ts
index e93ee85441a22..a5db4674a7d14 100644
--- a/src/plugins/data/common/search/expressions/essql.ts
+++ b/src/plugins/data/common/search/expressions/essql.ts
@@ -217,14 +217,14 @@ export const getEssqlFn = ({ getStartDependencies }: EssqlFnArguments) => {
               return throwError(() => error);
             }),
             tap({
-              next(finalResponse) {
+              next({ rawResponse, took }) {
                 logInspectorRequest()
                   .stats({
                     hits: {
                       label: i18n.translate('data.search.es_search.hitsLabel', {
                         defaultMessage: 'Hits',
                       }),
-                      value: `${finalResponse.rawResponse.rows.length}`,
+                      value: `${rawResponse.rows.length}`,
                       description: i18n.translate('data.search.es_search.hitsDescription', {
                         defaultMessage: 'The number of documents returned by the query.',
                       }),
@@ -235,7 +235,7 @@ export const getEssqlFn = ({ getStartDependencies }: EssqlFnArguments) => {
                       }),
                       value: i18n.translate('data.search.es_search.queryTimeValue', {
                         defaultMessage: '{queryTime}ms',
-                        values: { queryTime: finalResponse.took },
+                        values: { queryTime: took },
                       }),
                       description: i18n.translate('data.search.es_search.queryTimeDescription', {
                         defaultMessage:
@@ -245,10 +245,10 @@ export const getEssqlFn = ({ getStartDependencies }: EssqlFnArguments) => {
                     },
                   })
                   .json(params)
-                  .ok({ json: finalResponse });
+                  .ok({ json: rawResponse });
               },
               error(error) {
-                logInspectorRequest().json(params).error({ json: error });
+                logInspectorRequest().error({ json: error });
               },
             })
           );
diff --git a/src/plugins/data/common/search/types.ts b/src/plugins/data/common/search/types.ts
index b2f818acaa0ac..cedfa3ee02274 100644
--- a/src/plugins/data/common/search/types.ts
+++ b/src/plugins/data/common/search/types.ts
@@ -6,7 +6,6 @@
  * Side Public License, v 1.
  */
 
-import type { ConnectionRequestParams } from '@elastic/transport';
 import type { TransportRequestOptions } from '@elastic/elasticsearch';
 import type { KibanaExecutionContext } from '@kbn/core/public';
 import type { DataView } from '@kbn/data-views-plugin/common';
@@ -87,11 +86,6 @@ export interface IKibanaSearchResponse<RawResponse = any> {
    * The raw response returned by the internal search method (usually the raw ES response)
    */
   rawResponse: RawResponse;
-
-  /**
-   * HTTP request parameters from elasticsearch transport client t
-   */
-  requestParams?: ConnectionRequestParams;
 }
 
 export interface IKibanaSearchRequest<Params = any> {
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 414230b7e5add..00ed4226fea3d 100644
--- a/src/plugins/data/public/search/search_interceptor/search_interceptor.ts
+++ b/src/plugins/data/public/search/search_interceptor/search_interceptor.ts
@@ -29,7 +29,6 @@ import {
   takeUntil,
   tap,
 } from 'rxjs/operators';
-import type { ConnectionRequestParams } from '@elastic/transport';
 import { PublicMethodsOf } from '@kbn/utility-types';
 import type { HttpSetup, IHttpFetchError } from '@kbn/core-http-browser';
 import { BfetchRequestError } from '@kbn/bfetch-plugin/public';
@@ -305,38 +304,18 @@ export class SearchInterceptor {
 
     const cancel = () => id && !isSavedToBackground && sendCancelRequest();
 
-    // Async search requires a series of requests
-    // 1) POST /<index pattern>/_async_search/
-    // 2..n) GET /_async_search/<async search identifier>
-    //
-    // First request contains useful request params for tools like Inspector.
-    // Preserve and project first request params into responses.
-    let firstRequestParams: ConnectionRequestParams;
-
     return pollSearch(search, cancel, {
       pollInterval: this.deps.searchConfig.asyncSearch.pollInterval,
       ...options,
       abortSignal: searchAbortController.getSignal(),
     }).pipe(
       tap((response) => {
-        if (!firstRequestParams && response.requestParams) {
-          firstRequestParams = response.requestParams;
-        }
-
         id = response.id;
 
         if (isCompleteResponse(response)) {
           searchTracker?.complete();
         }
       }),
-      map((response) => {
-        return firstRequestParams
-          ? {
-              ...response,
-              requestParams: firstRequestParams,
-            }
-          : response;
-      }),
       catchError((e: Error) => {
         searchTracker?.error();
         cancel();
diff --git a/src/plugins/data/server/search/routes/bsearch.ts b/src/plugins/data/server/search/routes/bsearch.ts
index 7248206c8ee95..581920feef89d 100644
--- a/src/plugins/data/server/search/routes/bsearch.ts
+++ b/src/plugins/data/server/search/routes/bsearch.ts
@@ -8,7 +8,6 @@
 
 import { firstValueFrom } from 'rxjs';
 import { catchError } from 'rxjs/operators';
-import { errors } from '@elastic/elasticsearch';
 import { BfetchServerSetup } from '@kbn/bfetch-plugin/server';
 import type { ExecutionContextSetup } from '@kbn/core/server';
 import apm from 'elastic-apm-node';
@@ -48,12 +47,6 @@ export function registerBsearchRoute(
                   message: err.message,
                   statusCode: err.statusCode,
                   attributes: err.errBody?.error,
-                  // TODO remove 'instanceof errors.ResponseError' check when
-                  // eql strategy throws KbnServerError (like all of the other strategies)
-                  requestParams:
-                    err instanceof errors.ResponseError
-                      ? err.meta?.meta?.request?.params
-                      : err.requestParams,
                 };
               })
             )
diff --git a/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.ts b/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.ts
index 45a7b4d90cd41..d6f5d948c784a 100644
--- a/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.ts
+++ b/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.ts
@@ -77,10 +77,7 @@ export const eqlSearchStrategyProvider = (
               meta: true,
             });
 
-        return toEqlKibanaSearchResponse(
-          response as TransportResult<EqlSearchResponse>,
-          (response as TransportResult<EqlSearchResponse>).meta?.request?.params
-        );
+        return toEqlKibanaSearchResponse(response as TransportResult<EqlSearchResponse>);
       };
 
       const cancel = async () => {
diff --git a/src/plugins/data/server/search/strategies/eql_search/response_utils.ts b/src/plugins/data/server/search/strategies/eql_search/response_utils.ts
index 48c19c996fd52..f9bdf5bc7de30 100644
--- a/src/plugins/data/server/search/strategies/eql_search/response_utils.ts
+++ b/src/plugins/data/server/search/strategies/eql_search/response_utils.ts
@@ -6,7 +6,6 @@
  * Side Public License, v 1.
  */
 
-import type { ConnectionRequestParams } from '@elastic/transport';
 import type { TransportResult } from '@elastic/elasticsearch';
 import { EqlSearchResponse } from './types';
 import { EqlSearchStrategyResponse } from '../../../../common';
@@ -16,14 +15,12 @@ import { EqlSearchStrategyResponse } from '../../../../common';
  * (EQL does not provide _shard info, so total/loaded cannot be calculated.)
  */
 export function toEqlKibanaSearchResponse(
-  response: TransportResult<EqlSearchResponse>,
-  requestParams?: ConnectionRequestParams
+  response: TransportResult<EqlSearchResponse>
 ): EqlSearchStrategyResponse {
   return {
     id: response.body.id,
     rawResponse: response,
     isPartial: response.body.is_partial,
     isRunning: response.body.is_running,
-    ...(requestParams ? { requestParams } : {}),
   };
 }
diff --git a/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts b/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts
index 679bb5ae2a699..15a6a4df7eed8 100644
--- a/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts
+++ b/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts
@@ -113,7 +113,7 @@ describe('ES search strategy', () => {
       )
     );
     const [, searchOptions] = esClient.search.mock.calls[0];
-    expect(searchOptions).toEqual({ signal: undefined, maxRetries: 5, meta: true });
+    expect(searchOptions).toEqual({ signal: undefined, maxRetries: 5 });
   });
 
   it('can be aborted', async () => {
@@ -131,10 +131,7 @@ describe('ES search strategy', () => {
       ...params,
       track_total_hits: true,
     });
-    expect(esClient.search.mock.calls[0][1]).toEqual({
-      signal: expect.any(AbortSignal),
-      meta: true,
-    });
+    expect(esClient.search.mock.calls[0][1]).toEqual({ signal: expect.any(AbortSignal) });
   });
 
   it('throws normalized error if ResponseError is thrown', async () => {
diff --git a/src/plugins/data/server/search/strategies/es_search/es_search_strategy.ts b/src/plugins/data/server/search/strategies/es_search/es_search_strategy.ts
index 1dc9beb565c79..b2aed5804f248 100644
--- a/src/plugins/data/server/search/strategies/es_search/es_search_strategy.ts
+++ b/src/plugins/data/server/search/strategies/es_search/es_search_strategy.ts
@@ -50,13 +50,12 @@ export const esSearchStrategyProvider = (
           ...(terminateAfter ? { terminate_after: terminateAfter } : {}),
           ...requestParams,
         };
-        const { body, meta } = await esClient.asCurrentUser.search(params, {
+        const body = await esClient.asCurrentUser.search(params, {
           signal: abortSignal,
           ...transport,
-          meta: true,
         });
         const response = shimHitsTotal(body, options);
-        return toKibanaSearchResponse(response, meta?.request?.params);
+        return toKibanaSearchResponse(response);
       } catch (e) {
         throw getKbnServerError(e);
       }
diff --git a/src/plugins/data/server/search/strategies/es_search/response_utils.ts b/src/plugins/data/server/search/strategies/es_search/response_utils.ts
index 6e364cbbc40bd..4773b6df3bbaf 100644
--- a/src/plugins/data/server/search/strategies/es_search/response_utils.ts
+++ b/src/plugins/data/server/search/strategies/es_search/response_utils.ts
@@ -6,7 +6,6 @@
  * Side Public License, v 1.
  */
 
-import type { ConnectionRequestParams } from '@elastic/transport';
 import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
 import { ISearchOptions } from '../../../../common';
 
@@ -25,15 +24,11 @@ export function getTotalLoaded(response: estypes.SearchResponse<unknown>) {
  * Get the Kibana representation of this response (see `IKibanaSearchResponse`).
  * @internal
  */
-export function toKibanaSearchResponse(
-  rawResponse: estypes.SearchResponse<unknown>,
-  requestParams?: ConnectionRequestParams
-) {
+export function toKibanaSearchResponse(rawResponse: estypes.SearchResponse<unknown>) {
   return {
     rawResponse,
     isPartial: false,
     isRunning: false,
-    ...(requestParams ? { requestParams } : {}),
     ...getTotalLoaded(rawResponse),
   };
 }
diff --git a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts
index c8322d3083995..298933907b8bb 100644
--- a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts
+++ b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts
@@ -65,7 +65,7 @@ export const enhancedEsSearchStrategyProvider = (
             ...(await getDefaultAsyncSubmitParams(uiSettingsClient, searchConfig, options)),
             ...request.params,
           };
-      const { body, headers, meta } = id
+      const { body, headers } = id
         ? await client.asyncSearch.get(
             { ...params, id },
             { ...options.transport, signal: options.abortSignal, meta: true }
@@ -78,11 +78,7 @@ export const enhancedEsSearchStrategyProvider = (
 
       const response = shimHitsTotal(body.response, options);
 
-      return toAsyncKibanaSearchResponse(
-        { ...body, response },
-        headers?.warning,
-        meta?.request?.params
-      );
+      return toAsyncKibanaSearchResponse({ ...body, response }, headers?.warning);
     };
 
     const cancel = async () => {
@@ -135,10 +131,8 @@ export const enhancedEsSearchStrategyProvider = (
       );
 
       const response = esResponse.body as estypes.SearchResponse<any>;
-      const requestParams = esResponse.meta?.request?.params;
       return {
         rawResponse: shimHitsTotal(response, options),
-        ...(requestParams ? { requestParams } : {}),
         ...getTotalLoaded(response),
       };
     } catch (e) {
diff --git a/src/plugins/data/server/search/strategies/ese_search/response_utils.ts b/src/plugins/data/server/search/strategies/ese_search/response_utils.ts
index 5439e8a618dae..c9390a1b381d5 100644
--- a/src/plugins/data/server/search/strategies/ese_search/response_utils.ts
+++ b/src/plugins/data/server/search/strategies/ese_search/response_utils.ts
@@ -6,25 +6,19 @@
  * Side Public License, v 1.
  */
 
-import type { ConnectionRequestParams } from '@elastic/transport';
 import type { AsyncSearchResponse } from './types';
 import { getTotalLoaded } from '../es_search';
 
 /**
  * Get the Kibana representation of an async search response (see `IKibanaSearchResponse`).
  */
-export function toAsyncKibanaSearchResponse(
-  response: AsyncSearchResponse,
-  warning?: string,
-  requestParams?: ConnectionRequestParams
-) {
+export function toAsyncKibanaSearchResponse(response: AsyncSearchResponse, warning?: string) {
   return {
     id: response.id,
     rawResponse: response.response,
     isPartial: response.is_partial,
     isRunning: response.is_running,
     ...(warning ? { warning } : {}),
-    ...(requestParams ? { requestParams } : {}),
     ...getTotalLoaded(response.response),
   };
 }
diff --git a/src/plugins/data/server/search/strategies/esql_search/esql_search_strategy.ts b/src/plugins/data/server/search/strategies/esql_search/esql_search_strategy.ts
index e61feaba15668..7f3f6f521853d 100644
--- a/src/plugins/data/server/search/strategies/esql_search/esql_search_strategy.ts
+++ b/src/plugins/data/server/search/strategies/esql_search/esql_search_strategy.ts
@@ -32,7 +32,7 @@ export const esqlSearchStrategyProvider = (
     const search = async () => {
       try {
         const { terminateAfter, ...requestParams } = request.params ?? {};
-        const { headers, body, meta } = await esClient.asCurrentUser.transport.request(
+        const { headers, body } = await esClient.asCurrentUser.transport.request(
           {
             method: 'POST',
             path: '/_query',
@@ -45,12 +45,10 @@ export const esqlSearchStrategyProvider = (
             meta: true,
           }
         );
-        const transportRequestParams = meta?.request?.params;
         return {
           rawResponse: body,
           isPartial: false,
           isRunning: false,
-          ...(transportRequestParams ? { requestParams: transportRequestParams } : {}),
           warning: headers?.warning,
         };
       } catch (e) {
diff --git a/src/plugins/data/server/search/strategies/sql_search/response_utils.ts b/src/plugins/data/server/search/strategies/sql_search/response_utils.ts
index 0f4fb3e275f0e..b859df9db4237 100644
--- a/src/plugins/data/server/search/strategies/sql_search/response_utils.ts
+++ b/src/plugins/data/server/search/strategies/sql_search/response_utils.ts
@@ -6,7 +6,6 @@
  * Side Public License, v 1.
  */
 
-import type { ConnectionRequestParams } from '@elastic/transport';
 import { SqlQueryResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
 import { SqlSearchStrategyResponse } from '../../../../common';
 
@@ -16,8 +15,7 @@ import { SqlSearchStrategyResponse } from '../../../../common';
 export function toAsyncKibanaSearchResponse(
   response: SqlQueryResponse,
   startTime: number,
-  warning?: string,
-  requestParams?: ConnectionRequestParams
+  warning?: string
 ): SqlSearchStrategyResponse {
   return {
     id: response.id,
@@ -26,6 +24,5 @@ export function toAsyncKibanaSearchResponse(
     isRunning: response.is_running,
     took: Date.now() - startTime,
     ...(warning ? { warning } : {}),
-    ...(requestParams ? { requestParams } : {}),
   };
 }
diff --git a/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.ts b/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.ts
index b6207787d8fbb..c8928a343eec5 100644
--- a/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.ts
+++ b/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.ts
@@ -9,7 +9,6 @@
 import type { IncomingHttpHeaders } from 'http';
 import type { IScopedClusterClient, Logger } from '@kbn/core/server';
 import { catchError, tap } from 'rxjs/operators';
-import type { DiagnosticResult } from '@elastic/transport';
 import { SqlQueryResponse } from '@elastic/elasticsearch/lib/api/types';
 import { getKbnServerError } from '@kbn/kibana-utils-plugin/server';
 import type { ISearchStrategy, SearchStrategyDependencies } from '../../types';
@@ -49,10 +48,9 @@ export const sqlSearchStrategyProvider = (
       const { keep_cursor: keepCursor, ...params } = request.params ?? {};
       let body: SqlQueryResponse;
       let headers: IncomingHttpHeaders;
-      let meta: DiagnosticResult['meta'];
 
       if (id) {
-        ({ body, headers, meta } = await client.sql.getAsync(
+        ({ body, headers } = await client.sql.getAsync(
           {
             format: params?.format ?? 'json',
             ...getDefaultAsyncGetParams(searchConfig, options),
@@ -61,7 +59,7 @@ export const sqlSearchStrategyProvider = (
           { ...options.transport, signal: options.abortSignal, meta: true }
         ));
       } else {
-        ({ headers, body, meta } = await client.sql.query(
+        ({ headers, body } = await client.sql.query(
           {
             format: params.format ?? 'json',
             ...getDefaultAsyncSubmitParams(searchConfig, options),
@@ -81,7 +79,7 @@ export const sqlSearchStrategyProvider = (
         }
       }
 
-      return toAsyncKibanaSearchResponse(body, startTime, headers?.warning, meta?.request?.params);
+      return toAsyncKibanaSearchResponse(body, startTime, headers?.warning);
     };
 
     const cancel = async () => {
diff --git a/src/plugins/inspector/common/adapters/request/move_request_params_to_top_level.test.ts b/src/plugins/inspector/common/adapters/request/move_request_params_to_top_level.test.ts
deleted file mode 100644
index 37b7a8dd6f283..0000000000000
--- a/src/plugins/inspector/common/adapters/request/move_request_params_to_top_level.test.ts
+++ /dev/null
@@ -1,66 +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 { moveRequestParamsToTopLevel } from './move_request_params_to_top_level';
-import { RequestStatus } from './types';
-
-describe('moveRequestParamsToTopLevel', () => {
-  test('should move request meta from error response', () => {
-    expect(
-      moveRequestParamsToTopLevel(RequestStatus.ERROR, {
-        json: {
-          attributes: {},
-          err: {
-            message: 'simulated error',
-            requestParams: {
-              method: 'POST',
-              path: '/_query',
-            },
-          },
-        },
-        time: 1,
-      })
-    ).toEqual({
-      json: {
-        attributes: {},
-        err: {
-          message: 'simulated error',
-        },
-      },
-      requestParams: {
-        method: 'POST',
-        path: '/_query',
-      },
-      time: 1,
-    });
-  });
-
-  test('should move request meta from ok response', () => {
-    expect(
-      moveRequestParamsToTopLevel(RequestStatus.OK, {
-        json: {
-          rawResponse: {},
-          requestParams: {
-            method: 'POST',
-            path: '/_query',
-          },
-        },
-        time: 1,
-      })
-    ).toEqual({
-      json: {
-        rawResponse: {},
-      },
-      requestParams: {
-        method: 'POST',
-        path: '/_query',
-      },
-      time: 1,
-    });
-  });
-});
diff --git a/src/plugins/inspector/common/adapters/request/move_request_params_to_top_level.ts b/src/plugins/inspector/common/adapters/request/move_request_params_to_top_level.ts
deleted file mode 100644
index a00a2d90559c7..0000000000000
--- a/src/plugins/inspector/common/adapters/request/move_request_params_to_top_level.ts
+++ /dev/null
@@ -1,56 +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 type { ConnectionRequestParams } from '@elastic/transport';
-import { RequestStatus, Response } from './types';
-
-interface ErrorResponse {
-  [key: string]: unknown;
-  err?: {
-    [key: string]: unknown;
-    requestParams?: ConnectionRequestParams;
-  };
-}
-
-interface OkResponse {
-  [key: string]: unknown;
-  requestParams?: ConnectionRequestParams;
-}
-
-export function moveRequestParamsToTopLevel(status: RequestStatus, response: Response) {
-  if (status === RequestStatus.ERROR) {
-    const requestParams = (response.json as ErrorResponse)?.err?.requestParams;
-    if (!requestParams) {
-      return response;
-    }
-
-    const json = {
-      ...response.json,
-      err: { ...(response.json as ErrorResponse).err },
-    };
-    delete json.err.requestParams;
-    return {
-      ...response,
-      json,
-      requestParams,
-    };
-  }
-
-  const requestParams = (response.json as OkResponse)?.requestParams;
-  if (!requestParams) {
-    return response;
-  }
-
-  const json = { ...response.json } as OkResponse;
-  delete json.requestParams;
-  return {
-    ...response,
-    json,
-    requestParams,
-  };
-}
diff --git a/src/plugins/inspector/common/adapters/request/request_responder.ts b/src/plugins/inspector/common/adapters/request/request_responder.ts
index cf3a4b6c223da..1d3a999e4834d 100644
--- a/src/plugins/inspector/common/adapters/request/request_responder.ts
+++ b/src/plugins/inspector/common/adapters/request/request_responder.ts
@@ -8,7 +8,6 @@
 
 import { i18n } from '@kbn/i18n';
 import { Request, RequestStatistics, RequestStatus, Response } from './types';
-import { moveRequestParamsToTopLevel } from './move_request_params_to_top_level';
 
 /**
  * An API to specify information about a specific request that will be logged.
@@ -54,7 +53,7 @@ export class RequestResponder {
   public finish(status: RequestStatus, response: Response): void {
     this.request.time = response.time ?? Date.now() - this.request.startTime;
     this.request.status = status;
-    this.request.response = moveRequestParamsToTopLevel(status, response);
+    this.request.response = response;
     this.onChange();
   }
 
diff --git a/src/plugins/inspector/common/adapters/request/types.ts b/src/plugins/inspector/common/adapters/request/types.ts
index d00e1304f74f5..4e6a8d324559f 100644
--- a/src/plugins/inspector/common/adapters/request/types.ts
+++ b/src/plugins/inspector/common/adapters/request/types.ts
@@ -6,8 +6,6 @@
  * Side Public License, v 1.
  */
 
-import type { ConnectionRequestParams } from '@elastic/transport';
-
 /**
  * The status a request can have.
  */
@@ -54,8 +52,6 @@ export interface RequestStatistic {
 }
 
 export interface Response {
-  // TODO replace object with IKibanaSearchResponse once IKibanaSearchResponse is seperated from data plugin.
   json?: object;
-  requestParams?: ConnectionRequestParams;
   time?: number;
 }
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 58f5dd44f3f11..5ab50ba33a514 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
@@ -12,7 +12,6 @@
 /* eslint-disable @elastic/eui/href-or-on-click */
 
 import { EuiButtonEmpty, EuiCopy, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
-import type { ConnectionRequestParams } from '@elastic/transport';
 import { i18n } from '@kbn/i18n';
 import { XJsonLang } from '@kbn/monaco';
 import { compressToEncodedURIComponent } from 'lz-string';
@@ -22,7 +21,6 @@ import { InspectorPluginStartDeps } from '../../../../plugin';
 
 interface RequestCodeViewerProps {
   indexPattern?: string;
-  requestParams?: ConnectionRequestParams;
   json: string;
 }
 
@@ -41,37 +39,19 @@ const openInSearchProfilerLabel = i18n.translate('inspector.requests.openInSearc
 /**
  * @internal
  */
-export const RequestCodeViewer = ({
-  indexPattern,
-  requestParams,
-  json,
-}: RequestCodeViewerProps) => {
+export const RequestCodeViewer = ({ indexPattern, json }: RequestCodeViewerProps) => {
   const { services } = useKibana<InspectorPluginStartDeps>();
 
   const navigateToUrl = services.application?.navigateToUrl;
 
-  function getValue() {
-    if (!requestParams) {
-      return json;
-    }
-
-    const fullPath = requestParams.querystring
-      ? `${requestParams.path}?${requestParams.querystring}`
-      : requestParams.path;
-
-    return `${requestParams.method} ${fullPath}\n${json}`;
-  }
-
-  const value = getValue();
-
-  const devToolsDataUri = compressToEncodedURIComponent(value);
+  const devToolsDataUri = compressToEncodedURIComponent(`GET ${indexPattern}/_search\n${json}`);
   const consoleHref = services.share.url.locators
     .get('CONSOLE_APP_LOCATOR')
     ?.useUrl({ loadFrom: `data:text/plain,${devToolsDataUri}` });
   // Check if both the Dev Tools UI and the Console UI are enabled.
   const canShowDevTools =
     services.application?.capabilities?.dev_tools.show && consoleHref !== undefined;
-  const shouldShowDevToolsLink = !!(requestParams && canShowDevTools);
+  const shouldShowDevToolsLink = !!(indexPattern && canShowDevTools);
   const handleDevToolsLinkClick = useCallback(
     () => consoleHref && navigateToUrl && navigateToUrl(consoleHref),
     [consoleHref, navigateToUrl]
@@ -155,7 +135,7 @@ export const RequestCodeViewer = ({
       <EuiFlexItem grow={true} data-test-subj="inspectorRequestCodeViewerContainer">
         <CodeEditor
           languageId={XJsonLang.ID}
-          value={value}
+          value={json}
           options={{
             readOnly: true,
             lineNumbers: 'off',
diff --git a/src/plugins/inspector/public/views/requests/components/details/req_details_request.tsx b/src/plugins/inspector/public/views/requests/components/details/req_details_request.tsx
index 14b58eb1ee398..d340cba2b2aae 100644
--- a/src/plugins/inspector/public/views/requests/components/details/req_details_request.tsx
+++ b/src/plugins/inspector/public/views/requests/components/details/req_details_request.tsx
@@ -29,7 +29,6 @@ export class RequestDetailsRequest extends Component<RequestDetailsProps> {
     return (
       <RequestCodeViewer
         indexPattern={this.props.request.stats?.indexPattern?.value}
-        requestParams={this.props.request.response?.requestParams}
         json={JSON.stringify(json, null, 2)}
       />
     );
diff --git a/src/plugins/kibana_utils/server/report_server_error.ts b/src/plugins/kibana_utils/server/report_server_error.ts
index a9fd5d9265bd3..0fcc0c34cc4a9 100644
--- a/src/plugins/kibana_utils/server/report_server_error.ts
+++ b/src/plugins/kibana_utils/server/report_server_error.ts
@@ -7,22 +7,14 @@
  */
 
 import { errors } from '@elastic/elasticsearch';
-import type { ConnectionRequestParams } from '@elastic/transport';
 import { KibanaResponseFactory } from '@kbn/core/server';
 import { KbnError } from '../common';
 
 export class KbnServerError extends KbnError {
   public errBody?: Record<string, any>;
-  public requestParams?: ConnectionRequestParams;
-  constructor(
-    message: string,
-    public readonly statusCode: number,
-    errBody?: Record<string, any>,
-    requestParams?: ConnectionRequestParams
-  ) {
+  constructor(message: string, public readonly statusCode: number, errBody?: Record<string, any>) {
     super(message);
     this.errBody = errBody;
-    this.requestParams = requestParams;
   }
 }
 
@@ -36,8 +28,7 @@ export function getKbnServerError(e: Error) {
   return new KbnServerError(
     e.message ?? 'Unknown error',
     e instanceof errors.ResponseError ? e.statusCode! : 500,
-    e instanceof errors.ResponseError ? e.body : undefined,
-    e instanceof errors.ResponseError ? e.meta?.meta?.request?.params : undefined
+    e instanceof errors.ResponseError ? e.body : undefined
   );
 }
 
@@ -52,7 +43,6 @@ export function reportServerError(res: KibanaResponseFactory, err: KbnServerErro
     body: {
       message: err.message,
       attributes: err.errBody?.error,
-      ...(err.requestParams ? { requestParams: err.requestParams } : {}),
     },
   });
 }
diff --git a/test/api_integration/apis/search/bsearch.ts b/test/api_integration/apis/search/bsearch.ts
index 58f0765a53913..9ce10dc38a643 100644
--- a/test/api_integration/apis/search/bsearch.ts
+++ b/test/api_integration/apis/search/bsearch.ts
@@ -232,351 +232,6 @@ export default function ({ getService }: FtrProviderContext) {
           });
         });
       });
-
-      describe('request meta', () => {
-        describe('es', () => {
-          it(`should return request meta`, async () => {
-            const resp = await supertest
-              .post(`/internal/bsearch`)
-              .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
-              .send({
-                batch: [
-                  {
-                    request: {
-                      params: {
-                        index: '.kibana',
-                        body: {
-                          query: {
-                            match_all: {},
-                          },
-                        },
-                      },
-                    },
-                    options: {
-                      strategy: 'es',
-                    },
-                  },
-                ],
-              });
-
-            const jsonBody = parseBfetchResponse(resp);
-
-            expect(resp.status).to.be(200);
-            expect(jsonBody[0].result).to.have.property('requestParams');
-            expect(jsonBody[0].result.requestParams.method).to.be('POST');
-            expect(jsonBody[0].result.requestParams.path).to.be('/.kibana/_search');
-            expect(jsonBody[0].result.requestParams.querystring).to.be('ignore_unavailable=true');
-          });
-
-          it(`should return request meta when request fails`, async () => {
-            const resp = await supertest
-              .post(`/internal/bsearch`)
-              .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
-              .send({
-                batch: [
-                  {
-                    request: {
-                      params: {
-                        index: '.kibana',
-                        body: {
-                          query: {
-                            bool: {
-                              filter: [
-                                {
-                                  error_query: {
-                                    indices: [
-                                      {
-                                        error_type: 'exception',
-                                        message: 'simulated failure',
-                                        name: '.kibana',
-                                      },
-                                    ],
-                                  },
-                                },
-                              ],
-                            },
-                          },
-                        },
-                      },
-                    },
-                    options: {
-                      strategy: 'es',
-                    },
-                  },
-                ],
-              });
-
-            const jsonBody = parseBfetchResponse(resp);
-
-            expect(resp.status).to.be(200);
-            expect(jsonBody[0].error).to.have.property('requestParams');
-            expect(jsonBody[0].error.requestParams.method).to.be('POST');
-            expect(jsonBody[0].error.requestParams.path).to.be('/.kibana/_search');
-            expect(jsonBody[0].error.requestParams.querystring).to.be('ignore_unavailable=true');
-          });
-        });
-
-        describe('ese', () => {
-          it(`should return request meta`, async () => {
-            const resp = await supertest
-              .post(`/internal/bsearch`)
-              .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
-              .send({
-                batch: [
-                  {
-                    request: {
-                      params: {
-                        index: '.kibana',
-                        body: {
-                          query: {
-                            match_all: {},
-                          },
-                        },
-                      },
-                    },
-                    options: {
-                      strategy: 'ese',
-                    },
-                  },
-                ],
-              });
-
-            const jsonBody = parseBfetchResponse(resp);
-
-            expect(resp.status).to.be(200);
-            expect(jsonBody[0].result).to.have.property('requestParams');
-            expect(jsonBody[0].result.requestParams.method).to.be('POST');
-            expect(jsonBody[0].result.requestParams.path).to.be('/.kibana/_async_search');
-            expect(jsonBody[0].result.requestParams.querystring).to.be(
-              'batched_reduce_size=64&ccs_minimize_roundtrips=true&wait_for_completion_timeout=200ms&keep_on_completion=false&keep_alive=60000ms&ignore_unavailable=true'
-            );
-          });
-
-          it(`should return request meta when request fails`, async () => {
-            const resp = await supertest
-              .post(`/internal/bsearch`)
-              .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
-              .send({
-                batch: [
-                  {
-                    request: {
-                      params: {
-                        index: '.kibana',
-                        body: {
-                          bool: {
-                            filter: [
-                              {
-                                error_query: {
-                                  indices: [
-                                    {
-                                      error_type: 'exception',
-                                      message: 'simulated failure',
-                                      name: '.kibana',
-                                    },
-                                  ],
-                                },
-                              },
-                            ],
-                          },
-                        },
-                      },
-                    },
-                    options: {
-                      strategy: 'ese',
-                    },
-                  },
-                ],
-              });
-
-            const jsonBody = parseBfetchResponse(resp);
-
-            expect(resp.status).to.be(200);
-            expect(jsonBody[0].error).to.have.property('requestParams');
-            expect(jsonBody[0].error.requestParams.method).to.be('POST');
-            expect(jsonBody[0].error.requestParams.path).to.be('/.kibana/_async_search');
-            expect(jsonBody[0].error.requestParams.querystring).to.be(
-              'batched_reduce_size=64&ccs_minimize_roundtrips=true&wait_for_completion_timeout=200ms&keep_on_completion=false&keep_alive=60000ms&ignore_unavailable=true'
-            );
-          });
-        });
-
-        describe('esql', () => {
-          it(`should return request meta`, async () => {
-            const resp = await supertest
-              .post(`/internal/bsearch`)
-              .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
-              .send({
-                batch: [
-                  {
-                    request: {
-                      params: {
-                        query: 'from .kibana | limit 1',
-                      },
-                    },
-                    options: {
-                      strategy: 'esql',
-                    },
-                  },
-                ],
-              });
-
-            const jsonBody = parseBfetchResponse(resp);
-
-            expect(resp.status).to.be(200);
-            expect(jsonBody[0].result).to.have.property('requestParams');
-            expect(jsonBody[0].result.requestParams.method).to.be('POST');
-            expect(jsonBody[0].result.requestParams.path).to.be('/_query');
-            expect(jsonBody[0].result.requestParams.querystring).to.be('');
-          });
-
-          it(`should return request meta when request fails`, async () => {
-            const resp = await supertest
-              .post(`/internal/bsearch`)
-              .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
-              .send({
-                batch: [
-                  {
-                    request: {
-                      params: {
-                        query: 'fro .kibana | limit 1',
-                      },
-                    },
-                    options: {
-                      strategy: 'esql',
-                    },
-                  },
-                ],
-              });
-
-            const jsonBody = parseBfetchResponse(resp);
-
-            expect(resp.status).to.be(200);
-            expect(jsonBody[0].error).to.have.property('requestParams');
-            expect(jsonBody[0].error.requestParams.method).to.be('POST');
-            expect(jsonBody[0].error.requestParams.path).to.be('/_query');
-            expect(jsonBody[0].error.requestParams.querystring).to.be('');
-          });
-        });
-
-        describe('sql', () => {
-          it(`should return request meta`, async () => {
-            const resp = await supertest
-              .post(`/internal/bsearch`)
-              .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
-              .send({
-                batch: [
-                  {
-                    request: {
-                      params: {
-                        query: 'SELECT * FROM ".kibana" LIMIT 1',
-                      },
-                    },
-                    options: {
-                      strategy: 'sql',
-                    },
-                  },
-                ],
-              });
-
-            const jsonBody = parseBfetchResponse(resp);
-
-            expect(resp.status).to.be(200);
-            expect(jsonBody[0].result).to.have.property('requestParams');
-            expect(jsonBody[0].result.requestParams.method).to.be('POST');
-            expect(jsonBody[0].result.requestParams.path).to.be('/_sql');
-            expect(jsonBody[0].result.requestParams.querystring).to.be('format=json');
-          });
-
-          it(`should return request meta when request fails`, async () => {
-            const resp = await supertest
-              .post(`/internal/bsearch`)
-              .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
-              .send({
-                batch: [
-                  {
-                    request: {
-                      params: {
-                        query: 'SELEC * FROM ".kibana" LIMIT 1',
-                      },
-                    },
-                    options: {
-                      strategy: 'sql',
-                    },
-                  },
-                ],
-              });
-
-            const jsonBody = parseBfetchResponse(resp);
-
-            expect(resp.status).to.be(200);
-            expect(jsonBody[0].error).to.have.property('requestParams');
-            expect(jsonBody[0].error.requestParams.method).to.be('POST');
-            expect(jsonBody[0].error.requestParams.path).to.be('/_sql');
-            expect(jsonBody[0].error.requestParams.querystring).to.be('format=json');
-          });
-        });
-
-        describe('eql', () => {
-          it(`should return request meta`, async () => {
-            const resp = await supertest
-              .post(`/internal/bsearch`)
-              .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
-              .send({
-                batch: [
-                  {
-                    request: {
-                      params: {
-                        index: '.kibana',
-                        query: 'any where true',
-                        timestamp_field: 'created_at',
-                      },
-                    },
-                    options: {
-                      strategy: 'eql',
-                    },
-                  },
-                ],
-              });
-
-            const jsonBody = parseBfetchResponse(resp);
-
-            expect(resp.status).to.be(200);
-            expect(jsonBody[0].result).to.have.property('requestParams');
-            expect(jsonBody[0].result.requestParams.method).to.be('POST');
-            expect(jsonBody[0].result.requestParams.path).to.be('/.kibana/_eql/search');
-            expect(jsonBody[0].result.requestParams.querystring).to.be('ignore_unavailable=true');
-          });
-
-          it(`should return request meta when request fails`, async () => {
-            const resp = await supertest
-              .post(`/internal/bsearch`)
-              .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
-              .send({
-                batch: [
-                  {
-                    request: {
-                      params: {
-                        index: '.kibana',
-                        query: 'any where true',
-                      },
-                    },
-                    options: {
-                      strategy: 'eql',
-                    },
-                  },
-                ],
-              });
-
-            const jsonBody = parseBfetchResponse(resp);
-
-            expect(resp.status).to.be(200);
-            expect(jsonBody[0].error).to.have.property('requestParams');
-            expect(jsonBody[0].error.requestParams.method).to.be('POST');
-            expect(jsonBody[0].error.requestParams.path).to.be('/.kibana/_eql/search');
-            expect(jsonBody[0].error.requestParams.querystring).to.be('ignore_unavailable=true');
-          });
-        });
-      });
     });
   });
 }
diff --git a/test/functional/apps/visualize/group2/_inspector.ts b/test/functional/apps/visualize/group2/_inspector.ts
index 077a37a90c06c..80cfc42ab3cd6 100644
--- a/test/functional/apps/visualize/group2/_inspector.ts
+++ b/test/functional/apps/visualize/group2/_inspector.ts
@@ -14,6 +14,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
   const log = getService('log');
   const inspector = getService('inspector');
   const filterBar = getService('filterBar');
+  const monacoEditor = getService('monacoEditor');
   const PageObjects = getPageObjects(['visualize', 'visEditor', 'visChart', 'timePicker']);
 
   describe('inspector', function describeIndexTests() {
@@ -40,8 +41,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
 
         await inspector.open();
         await inspector.openInspectorRequestsView();
-        const { body } = await inspector.getRequest(1);
-        expect(body.aggs['2'].max).property('missing', 10);
+        const requestTab = await inspector.getOpenRequestDetailRequestButton();
+        await requestTab.click();
+        const requestJSON = JSON.parse(await monacoEditor.getCodeEditorValue(1));
+
+        expect(requestJSON.aggs['2'].max).property('missing', 10);
       });
 
       after(async () => {
diff --git a/test/functional/services/inspector.ts b/test/functional/services/inspector.ts
index 7313187047a18..6222405aa6dae 100644
--- a/test/functional/services/inspector.ts
+++ b/test/functional/services/inspector.ts
@@ -299,21 +299,6 @@ export class InspectorService extends FtrService {
     return this.testSubjects.find('inspectorRequestDetailResponse');
   }
 
-  public async getRequest(
-    codeEditorIndex: number = 0
-  ): Promise<{ command: string; body: Record<string, any> }> {
-    await (await this.getOpenRequestDetailRequestButton()).click();
-
-    await this.monacoEditor.waitCodeEditorReady('inspectorRequestCodeViewerContainer');
-    const requestString = await this.monacoEditor.getCodeEditorValue(codeEditorIndex);
-    this.log.debug('Request string from inspector:', requestString);
-    const openBraceIndex = requestString.indexOf('{');
-    return {
-      command: openBraceIndex >= 0 ? requestString.substring(0, openBraceIndex).trim() : '',
-      body: openBraceIndex >= 0 ? JSON.parse(requestString.substring(openBraceIndex)) : {},
-    };
-  }
-
   public async getResponse(): Promise<Record<string, any>> {
     await (await this.getOpenRequestDetailResponseButton()).click();
 

From a42d601fe5d5a4b63dd8ac6d0c0a70a2d8ddeaad Mon Sep 17 00:00:00 2001
From: Josh Dover <1813008+joshdover@users.noreply.github.com>
Date: Thu, 28 Sep 2023 14:34:50 +0200
Subject: [PATCH 15/20] [Fleet] Add retries w/ backoff to Fleet setup on Kibana
 boot (#167246)

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Julia Bardi <90178898+juliaElastic@users.noreply.github.com>
---
 config/serverless.yml                         |  1 +
 package.json                                  |  1 +
 x-pack/plugins/fleet/common/types/index.ts    |  1 +
 x-pack/plugins/fleet/server/config.ts         |  1 +
 x-pack/plugins/fleet/server/plugin.ts         | 40 ++++++++++++++++---
 .../epm/packages/_install_package.test.ts     |  3 ++
 6 files changed, 42 insertions(+), 5 deletions(-)

diff --git a/config/serverless.yml b/config/serverless.yml
index 33eaec2b22b6a..8319d4c0ecee9 100644
--- a/config/serverless.yml
+++ b/config/serverless.yml
@@ -7,6 +7,7 @@ xpack.fleet.internal.disableILMPolicies: true
 xpack.fleet.internal.disableProxies: true
 xpack.fleet.internal.activeAgentsSoftLimit: 25000
 xpack.fleet.internal.onlyAllowAgentUpgradeToKnownVersions: true
+xpack.fleet.internal.retrySetupOnBoot: true
 
 # Cloud links
 xpack.cloud.base_url: 'https://cloud.elastic.co'
diff --git a/package.json b/package.json
index 313ee86a8171d..4792c66f475ab 100644
--- a/package.json
+++ b/package.json
@@ -888,6 +888,7 @@
     "email-addresses": "^5.0.0",
     "execa": "^5.1.1",
     "expiry-js": "0.1.7",
+    "exponential-backoff": "^3.1.1",
     "extract-zip": "^2.0.1",
     "fast-deep-equal": "^3.1.1",
     "fflate": "^0.6.9",
diff --git a/x-pack/plugins/fleet/common/types/index.ts b/x-pack/plugins/fleet/common/types/index.ts
index 47f62c8e19794..6ef34d045e20f 100644
--- a/x-pack/plugins/fleet/common/types/index.ts
+++ b/x-pack/plugins/fleet/common/types/index.ts
@@ -51,6 +51,7 @@ export interface FleetConfigType {
     fleetServerStandalone: boolean;
     onlyAllowAgentUpgradeToKnownVersions: boolean;
     activeAgentsSoftLimit?: number;
+    retrySetupOnBoot: boolean;
     registry: {
       kibanaVersionCheckEnabled: boolean;
       capabilities: string[];
diff --git a/x-pack/plugins/fleet/server/config.ts b/x-pack/plugins/fleet/server/config.ts
index b68684460bf81..f1210a49f7f0c 100644
--- a/x-pack/plugins/fleet/server/config.ts
+++ b/x-pack/plugins/fleet/server/config.ts
@@ -188,6 +188,7 @@ export const config: PluginConfigDescriptor = {
               min: 0,
             })
           ),
+          retrySetupOnBoot: schema.boolean({ defaultValue: false }),
           registry: schema.object(
             {
               kibanaVersionCheckEnabled: schema.boolean({ defaultValue: true }),
diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts
index 9603eb2b47064..e0aa5315d8ff9 100644
--- a/x-pack/plugins/fleet/server/plugin.ts
+++ b/x-pack/plugins/fleet/server/plugin.ts
@@ -5,6 +5,7 @@
  * 2.0.
  */
 
+import { backOff } from 'exponential-backoff';
 import type { Observable } from 'rxjs';
 import { BehaviorSubject } from 'rxjs';
 import { take, filter } from 'rxjs/operators';
@@ -532,9 +533,39 @@ export class FleetPlugin
           )
           .toPromise();
 
-        await setupFleet(
-          new SavedObjectsClient(core.savedObjects.createInternalRepository()),
-          core.elasticsearch.client.asInternalUser
+        // Retry Fleet setup w/ backoff
+        await backOff(
+          async () => {
+            await setupFleet(
+              new SavedObjectsClient(core.savedObjects.createInternalRepository()),
+              core.elasticsearch.client.asInternalUser
+            );
+          },
+          {
+            // We only retry when this feature flag is enabled
+            numOfAttempts: this.configInitialValue.internal?.retrySetupOnBoot ? Infinity : 1,
+            // 250ms initial backoff
+            startingDelay: 250,
+            // 5m max backoff
+            maxDelay: 60000 * 5,
+            timeMultiple: 2,
+            // avoid HA contention with other Kibana instances
+            jitter: 'full',
+            retry: (error: any, attemptCount: number) => {
+              const summary = `Fleet setup attempt ${attemptCount} failed, will retry after backoff`;
+              logger.debug(summary, { error: { message: error } });
+
+              this.fleetStatus$.next({
+                level: ServiceStatusLevels.available,
+                summary,
+                meta: {
+                  attemptCount,
+                  error,
+                },
+              });
+              return true;
+            },
+          }
         );
 
         this.fleetStatus$.next({
@@ -542,8 +573,7 @@ export class FleetPlugin
           summary: 'Fleet is available',
         });
       } catch (error) {
-        logger.warn('Fleet setup failed');
-        logger.warn(error);
+        logger.warn('Fleet setup failed', { error: { message: error } });
 
         this.fleetStatus$.next({
           // As long as Fleet has a dependency on EPR, we can't reliably set Kibana status to `unavailable` here.
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts
index b7fe0d95310ef..af3460e266af1 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts
@@ -135,6 +135,7 @@ describe('_installPackage', () => {
           disableProxies: false,
           fleetServerStandalone: false,
           onlyAllowAgentUpgradeToKnownVersions: false,
+          retrySetupOnBoot: false,
           registry: {
             kibanaVersionCheckEnabled: true,
             capabilities: [],
@@ -192,6 +193,7 @@ describe('_installPackage', () => {
           disableILMPolicies: false,
           fleetServerStandalone: false,
           onlyAllowAgentUpgradeToKnownVersions: false,
+          retrySetupOnBoot: false,
           registry: {
             kibanaVersionCheckEnabled: true,
             capabilities: [],
@@ -265,6 +267,7 @@ describe('_installPackage', () => {
             disableProxies: false,
             fleetServerStandalone: false,
             onlyAllowAgentUpgradeToKnownVersions: false,
+            retrySetupOnBoot: false,
             registry: {
               kibanaVersionCheckEnabled: true,
               capabilities: [],

From 833c07536267141ab9c53335e000698f74ce22d3 Mon Sep 17 00:00:00 2001
From: Marco Antonio Ghiani <marcoantonio.ghiani01@gmail.com>
Date: Thu, 28 Sep 2023 14:54:40 +0200
Subject: [PATCH 16/20] [Stack Monitoring] Update flows for cpu stats fetching
 (#167244)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

## 📓 Summary

When retrieving the CPU stats for containerized (or non-container)
clusters, we were not considering a scenario where the user could run in
a cgroup but without limits set.
These changes re-write the conditions to determine whether we allow
treating limitless containers as non-containerized, covering the case
where a user run in a cgroup and for some reason hasn't set the limit.

## Testing

> Taken from https://github.com/elastic/kibana/pull/159351 since it
reproduced the same behaviours

There are 3 main states to test:
No limit set but Kibana configured to use container stats.
Limit changed during lookback period (to/from real value, to/from no
limit).
Limit set and CPU usage crossing threshold and then falling down to
recovery

**Note: Please also test the non-container use case for this rule to
ensure that didn't get broken during this refactor**

**1. Start Elasticsearch in a container without setting the CPU
limits:**
```
docker network create elastic
docker run --name es01 --net elastic -p 9201:9200 -e xpack.license.self_generated.type=trial -it docker.elastic.co/elasticsearch/elasticsearch:master-SNAPSHOT
```

(We're using `master-SNAPSHOT` to include a recent fix to reporting for
cgroup v2)

Make note of the generated password for the `elastic` user.

**2. Start another Elasticsearch instance to act as the monitoring
cluster**

**3. Configure Kibana to connect to the monitoring cluster and start
it**

**4. Configure Metricbeat to collect metrics from the Docker cluster and
ship them to the monitoring cluster, then start it**

Execute the below command next to the Metricbeat binary to grab the CA
certificate from the Elasticsearch cluster.

```
docker cp es01:/usr/share/elasticsearch/config/certs/http_ca.crt .
```

Use the `elastic` password and the CA certificate to configure the
`elasticsearch` module:
```
  - module: elasticsearch
    xpack.enabled: true
    period: 10s
    hosts:
      - "https://localhost:9201"
    username: "elastic"
    password: "PASSWORD"
    ssl.certificate_authorities: "PATH_TO_CERT/http_ca.crt"
```

**5. Configure an alert in Kibana with a chosen threshold**

OBSERVE: Alert gets fired to inform you that there looks to be a
misconfiguration, together with reporting the current value for the
fallback metric (warning if the fallback metric is below threshold,
danger is if is above).

**6. Set limit**
First stop ES using `docker stop es01`, then set the limit using `docker
update --cpus=1 es01` and start it again using `docker start es01`.
After a brief delay you should now see the alert change to a warning
about the limits having changed during the alert lookback period and
stating that the CPU usage could not be confidently calculated.
Wait for change event to pass out of lookback window.

**7. Generate load on the monitored cluster**

[Slingshot](https://github.com/elastic/slingshot) is an option. After
you clone it, you need to update the `package.json` to match [this
change](https://github.com/elastic/slingshot/blob/8bfa8351deb0d89859548ee5241e34d0920927e5/package.json#L45-L46)
before running `npm install`.

Then you can modify the value for `elasticsearch` in the
`configs/hosts.json` file like this:
```
"elasticsearch": {
    "node": "https://localhost:9201",
    "auth": {
      "username": "elastic",
      "password": "PASSWORD"
    },
    "ssl": {
      "ca": "PATH_TO_CERT/http_ca.crt",
      "rejectUnauthorized": false
    }
  }
```

Then you can start one or more instances of Slingshot like this:
`npx ts-node bin/slingshot load --config configs/hosts.json`

**7. Observe the alert firing in the logs**
Assuming you're using a connector for server log output, you should see
a message like below once the threshold is breached:
```
`[2023-06-13T13:05:50.036+02:00][INFO ][plugins.actions.server-log] Server log: CPU usage alert is firing for node e76ce10526e2 in cluster: docker-cluster. [View node](/app/monitoring#/elasticsearch/nodes/OyDWTz1PS-aEwjqcPN2vNQ?_g=(cluster_uuid:kasJK8VyTG6xNZ2PFPAtYg))`
```

The alert should also be visible in the Stack Monitoring UI overview
page.

At this point you can stop Slingshot and confirm that the alert recovers
once CPU usage goes back down below the threshold.

**8. Stop the load and confirm that the rule recovers.**

---------

Co-authored-by: Marco Antonio Ghiani <marcoantonio.ghiani@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
 .../plugins/monitoring/common/types/alerts.ts |  1 -
 .../server/alerts/cpu_usage_rule.test.ts      | 40 ++++++---
 .../server/alerts/cpu_usage_rule.ts           | 27 +-----
 .../alerts/fetch_cpu_usage_node_stats.test.ts | 83 +-----------------
 .../lib/alerts/fetch_cpu_usage_node_stats.ts  | 86 +++++--------------
 5 files changed, 56 insertions(+), 181 deletions(-)

diff --git a/x-pack/plugins/monitoring/common/types/alerts.ts b/x-pack/plugins/monitoring/common/types/alerts.ts
index 71943f42dd21f..adf00789d4056 100644
--- a/x-pack/plugins/monitoring/common/types/alerts.ts
+++ b/x-pack/plugins/monitoring/common/types/alerts.ts
@@ -171,7 +171,6 @@ export interface AlertNodeStats {
 export interface AlertCpuUsageNodeStats extends AlertNodeStats {
   cpuUsage?: number;
   limitsChanged?: boolean;
-  missingLimits?: boolean;
   unexpectedLimits?: boolean;
 }
 
diff --git a/x-pack/plugins/monitoring/server/alerts/cpu_usage_rule.test.ts b/x-pack/plugins/monitoring/server/alerts/cpu_usage_rule.test.ts
index 171daed9f9d64..dcf1e80583726 100644
--- a/x-pack/plugins/monitoring/server/alerts/cpu_usage_rule.test.ts
+++ b/x-pack/plugins/monitoring/server/alerts/cpu_usage_rule.test.ts
@@ -258,12 +258,7 @@ describe('CpuUsageRule', () => {
 
     it('should fire actions when resource limits are missing', async () => {
       (fetchCpuUsageNodeStats as jest.Mock).mockImplementation(() => {
-        return [
-          {
-            ...stat,
-            missingLimits: true,
-          },
-        ];
+        return [stat];
       });
 
       const rule = new CpuUsageRule();
@@ -287,14 +282,39 @@ describe('CpuUsageRule', () => {
               nodeId,
               nodeName,
               threshold,
-              missingLimits: true,
             },
             nodeId,
             nodeName,
             ui: {
               isFiring: true,
               message: {
-                text: `Kibana is configured for containerized workloads but node #start_linkmyNodeName#end_link does not have resource limits configured. Fallback metric reports usage of ${cpuUsage}%. Last checked at #absolute`,
+                text: `Node #start_link${nodeName}#end_link is reporting CPU usage of ${cpuUsage}% which is above the configured threshold of ${threshold}%. Last checked at #absolute`,
+                nextSteps: [
+                  {
+                    text: '#start_linkCheck hot threads#end_link',
+                    tokens: [
+                      {
+                        startToken: '#start_link',
+                        endToken: '#end_link',
+                        type: 'docLink',
+                        partialUrl:
+                          '{elasticWebsiteUrl}guide/en/elasticsearch/reference/{docLinkVersion}/cluster-nodes-hot-threads.html',
+                      },
+                    ],
+                  },
+                  {
+                    text: '#start_linkCheck long running tasks#end_link',
+                    tokens: [
+                      {
+                        startToken: '#start_link',
+                        endToken: '#end_link',
+                        type: 'docLink',
+                        partialUrl:
+                          '{elasticWebsiteUrl}guide/en/elasticsearch/reference/{docLinkVersion}/tasks.html',
+                      },
+                    ],
+                  },
+                ],
                 tokens: [
                   {
                     startToken: '#start_link',
@@ -319,8 +339,8 @@ describe('CpuUsageRule', () => {
         ],
       });
       expect(scheduleActions).toHaveBeenCalledWith('default', {
-        internalFullMessage: `CPU usage alert for node ${nodeName} in cluster ${clusterName} faced issues while evaluating the usage. [View node](http://localhost:5601/app/monitoring#/elasticsearch/nodes/${nodeId}?_g=(cluster_uuid:${clusterUuid}))`,
-        internalShortMessage: `CPU usage alert for node ${nodeName} in cluster ${clusterName} faced issues while evaluating the usage. Verify CPU usage of node.`,
+        internalFullMessage: `CPU usage alert is firing for node ${nodeName} in cluster ${clusterName}. [View node](http://localhost:5601/app/monitoring#/elasticsearch/nodes/${nodeId}?_g=(cluster_uuid:${clusterUuid}))`,
+        internalShortMessage: `CPU usage alert is firing for node ${nodeName} in cluster ${clusterName}. Verify CPU usage of node.`,
         action: `[View node](http://localhost:5601/app/monitoring#/elasticsearch/nodes/${nodeId}?_g=(cluster_uuid:${clusterUuid}))`,
         actionPlain: 'Verify CPU usage of node.',
         clusterName,
diff --git a/x-pack/plugins/monitoring/server/alerts/cpu_usage_rule.ts b/x-pack/plugins/monitoring/server/alerts/cpu_usage_rule.ts
index 58265dbfdbad7..49ab66f2ce10d 100644
--- a/x-pack/plugins/monitoring/server/alerts/cpu_usage_rule.ts
+++ b/x-pack/plugins/monitoring/server/alerts/cpu_usage_rule.ts
@@ -100,12 +100,7 @@ export class CpuUsageRule extends BaseRule {
     stat: AlertCpuUsageNodeStats,
     threshold: number
   ): { shouldFire: boolean; severity: AlertSeverity } {
-    if (
-      stat.missingLimits ||
-      stat.limitsChanged ||
-      stat.unexpectedLimits ||
-      stat.cpuUsage === undefined
-    ) {
+    if (stat.limitsChanged || stat.unexpectedLimits || stat.cpuUsage === undefined) {
       let severity = AlertSeverity.Warning;
       if (stat.cpuUsage && stat.cpuUsage > threshold) {
         severity = AlertSeverity.Danger;
@@ -149,19 +144,6 @@ export class CpuUsageRule extends BaseRule {
       } as AlertMessageTimeToken,
     ];
 
-    if (stat.missingLimits) {
-      return {
-        text: i18n.translate('xpack.monitoring.alerts.cpuUsage.ui.missingLimits', {
-          defaultMessage: `Kibana is configured for containerized workloads but node #start_link{nodeName}#end_link does not have resource limits configured. Fallback metric reports usage of {cpuUsage}%. Last checked at #absolute`,
-          values: {
-            nodeName: stat.nodeName,
-            cpuUsage: numeral(stat.cpuUsage).format(ROUNDED_FLOAT),
-          },
-        }),
-        tokens,
-      };
-    }
-
     if (stat.unexpectedLimits) {
       return {
         text: i18n.translate('xpack.monitoring.alerts.cpuUsage.ui.unexpectedLimits', {
@@ -273,12 +255,7 @@ export class CpuUsageRule extends BaseRule {
   private getMessage(state: AlertCpuUsageState, clusterName: string, action: string) {
     const stat = state.meta as AlertCpuUsageNodeStats;
 
-    if (
-      stat.missingLimits ||
-      stat.limitsChanged ||
-      stat.unexpectedLimits ||
-      stat.cpuUsage === undefined
-    ) {
+    if (stat.limitsChanged || stat.unexpectedLimits || stat.cpuUsage === undefined) {
       return i18n.translate('xpack.monitoring.alerts.cpuUsage.firing.internalMessageForFailure', {
         defaultMessage: `CPU usage alert for node {nodeName} in cluster {clusterName} faced issues while evaluating the usage. {action}`,
         values: {
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts
index 9551b30d1c2d2..214a7c04005f5 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts
@@ -126,10 +126,10 @@ describe('fetchCpuUsageNodeStats', () => {
                         value: 45,
                       },
                       quota_micros_max: {
-                        value: -1,
+                        value: 2000,
                       },
                       quota_micros_min: {
-                        value: -1,
+                        value: 2000,
                       },
                       name: {
                         buckets: [
@@ -366,7 +366,6 @@ describe('fetchCpuUsageNodeStats', () => {
 
       expect(stats).toEqual([
         {
-          missingLimits: true,
           clusterUuid: 'my-test-cluster',
           nodeId: 'my-test-node',
           nodeName: 'test-node',
@@ -454,83 +453,5 @@ describe('fetchCpuUsageNodeStats', () => {
         },
       ]);
     });
-
-    it('warns about failing to compute usage due to values missing', async () => {
-      esClient.search.mockResponse({
-        aggregations: {
-          clusters: {
-            buckets: [
-              {
-                key: 'my-test-cluster',
-                nodes: {
-                  buckets: [
-                    {
-                      key: 'my-test-node',
-                      min_usage_nanos: {
-                        value: null,
-                      },
-                      max_usage_nanos: {
-                        value: null,
-                      },
-                      min_periods: {
-                        value: null,
-                      },
-                      max_periods: {
-                        value: null,
-                      },
-                      quota_micros_min: {
-                        value: 10000,
-                      },
-                      quota_micros_max: {
-                        value: 10000,
-                      },
-                      average_cpu_usage_percent: {
-                        value: 45,
-                      },
-                      name: {
-                        buckets: [
-                          {
-                            key: 'test-node',
-                          },
-                        ],
-                      },
-                      index: {
-                        buckets: [
-                          {
-                            key: 'a-local-index',
-                          },
-                        ],
-                      },
-                    },
-                  ],
-                },
-              },
-            ],
-          },
-        },
-      } as any);
-
-      const stats = await fetchCpuUsageNodeStats(
-        {
-          esClient,
-          clusterUuids: ['my-test-cluster'],
-          startMs: 0,
-          endMs: 10,
-          filterQuery,
-          logger: loggerMock.create(),
-        },
-        configSlice
-      );
-
-      expect(stats).toEqual([
-        {
-          clusterUuid: 'my-test-cluster',
-          nodeId: 'my-test-node',
-          nodeName: 'test-node',
-          ccs: undefined,
-          cpuUsage: undefined,
-        },
-      ]);
-    });
   });
 });
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts
index 014c38f447e1e..5ccaa522c7368 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts
@@ -14,14 +14,6 @@ import { MonitoringConfig } from '../../config';
 import { getElasticsearchDataset, getIndexPatterns } from '../cluster/get_index_patterns';
 import { createDatasetFilter } from './create_dataset_query_filter';
 
-interface CpuUsageFieldsWithValues {
-  'max of node_stats.os.cgroup.cpu.cfs_quota_micros': number | null;
-  'max of node_stats.os.cgroup.cpuacct.usage_nanos': number | null;
-  'min of node_stats.os.cgroup.cpuacct.usage_nanos': number | null;
-  'max of node_stats.os.cgroup.cpu.stat.number_of_elapsed_periods': number | null;
-  'min of node_stats.os.cgroup.cpu.stat.number_of_elapsed_periods': number | null;
-}
-
 interface Options {
   esClient: ElasticsearchClient;
   clusterUuids: string[];
@@ -45,7 +37,7 @@ export async function fetchCpuUsageNodeStats(
 }
 
 async function fetchContainerStats(
-  { esClient, startMs, endMs, clusterUuids, filterQuery, logger }: Options,
+  { esClient, startMs, endMs, clusterUuids, filterQuery }: Options,
   config: MonitoringConfig
 ) {
   const indexPatterns = getIndexPatterns({
@@ -178,58 +170,34 @@ async function fetchContainerStats(
         ccs = index.includes(':') ? index.split(':')[0] : undefined;
       }
 
-      const limitsNotSet = node.quota_micros_max.value === -1 && node.quota_micros_min.value === -1;
-      const notRunningInAContainer =
-        node.quota_micros_min.value === null && node.quota_micros_max.value === null;
-      if (limitsNotSet || notRunningInAContainer) {
-        return {
-          missingLimits: true,
-          clusterUuid: cluster.key as string,
-          nodeId: node.key as string,
-          cpuUsage: node.average_cpu_usage_percent.value ?? undefined,
-          nodeName,
-          ccs,
-        };
-      }
+      const nodeStats = {
+        clusterUuid: cluster.key as string,
+        nodeId: node.key as string,
+        nodeName,
+        ccs,
+      };
 
-      if (node.quota_micros_min.value !== node.quota_micros_max.value) {
-        return {
-          limitsChanged: true,
-          clusterUuid: cluster.key as string,
-          nodeId: node.key as string,
-          cpuUsage: undefined,
-          nodeName,
-          ccs,
-        };
-      }
+      const limitsNotSet = node.quota_micros_max.value === -1 && node.quota_micros_min.value === -1;
 
       if (
+        limitsNotSet ||
         node.max_usage_nanos.value === null ||
         node.min_usage_nanos.value === null ||
         node.max_periods.value === null ||
         node.min_periods.value === null ||
         node.quota_micros_max.value === null
       ) {
-        logger.warn(
-          `CPU usage rule: Some aggregated values needed for container CPU usage calculation was empty: ${findEmptyValues(
-            {
-              'max of node_stats.os.cgroup.cpu.cfs_quota_micros': node.quota_micros_max.value,
-              'max of node_stats.os.cgroup.cpuacct.usage_nanos': node.max_usage_nanos.value,
-              'min of node_stats.os.cgroup.cpuacct.usage_nanos': node.min_usage_nanos.value,
-              'max of node_stats.os.cgroup.cpu.stat.number_of_elapsed_periods':
-                node.max_periods.value,
-              'min of node_stats.os.cgroup.cpu.stat.number_of_elapsed_periods':
-                node.min_periods.value,
-            }
-          )}`
-        );
+        return {
+          ...nodeStats,
+          cpuUsage: node.average_cpu_usage_percent.value ?? undefined,
+        };
+      }
 
+      if (node.quota_micros_min.value !== node.quota_micros_max.value) {
         return {
-          clusterUuid: cluster.key as string,
-          nodeId: node.key as string,
+          ...nodeStats,
+          limitsChanged: true,
           cpuUsage: undefined,
-          nodeName,
-          ccs,
         };
       }
 
@@ -243,24 +211,13 @@ async function fetchContainerStats(
       );
 
       return {
-        clusterUuid: cluster.key as string,
-        nodeId: node.key as string,
+        ...nodeStats,
         cpuUsage: Math.round(cpuUsage * 100) / 100,
-        nodeName,
-        ccs,
       };
     });
   });
 }
 
-function findEmptyValues(fieldsWithValues: CpuUsageFieldsWithValues): string {
-  const entries: Array<[string, number | null]> = Object.entries(fieldsWithValues);
-  return entries
-    .filter(([, value]) => value === null)
-    .map(([key]) => key)
-    .join(', ');
-}
-
 function computeCfsPercentCpuUsage(usageNanos: number, quotaMicros: number, periods: number) {
   // See https://github.com/elastic/kibana/pull/159351 for an explanation of this formula
   const quotaNanos = quotaMicros * 1000;
@@ -380,8 +337,9 @@ async function fetchNonContainerStats(
         ccs = index.includes(':') ? index.split(':')[0] : undefined;
       }
 
-      const runningInAContainer =
-        node.quota_micros_min.value !== null || node.quota_micros_max.value !== null;
+      const runningInAContainerWithLimits =
+        (node.quota_micros_min.value !== null && node.quota_micros_min.value !== -1) ||
+        (node.quota_micros_max.value !== null && node.quota_micros_max.value !== -1);
 
       return {
         clusterUuid: cluster.key as string,
@@ -389,7 +347,7 @@ async function fetchNonContainerStats(
         cpuUsage: node.average_cpu.value ?? undefined,
         nodeName,
         ccs,
-        unexpectedLimits: runningInAContainer,
+        unexpectedLimits: runningInAContainerWithLimits,
       };
     });
   });

From 1f593f4c96fb6e9eb2ca24ee366fdcae22c65f0b Mon Sep 17 00:00:00 2001
From: Tomasz Ciecierski <tomasz.ciecierski@elastic.co>
Date: Thu, 28 Sep 2023 15:06:58 +0200
Subject: [PATCH 17/20] [EDR Workflows] FIX Cypress DW not creating Fleet
 server (#167378)

We realized that our 'real agent' tests in DW Cypress do not run
locally, because FS is not being created (normally created in a
before:run task).
The `before:run` task in cypress was not run.
According to the
[docs](https://docs.cypress.io/api/plugins/before-run-api), it requires
an experimental flag `experimentalInteractiveRunEvents` enabled and it
was removed in
[here](https://github.com/elastic/kibana/pull/166757/files#diff-c2e90a076b73f17a80dcb5bfaf96f143d4d0de4dfaa68f5085608621a86c612dL33)
---
 .../public/management/cypress/cypress_base.config.ts            | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/x-pack/plugins/security_solution/public/management/cypress/cypress_base.config.ts b/x-pack/plugins/security_solution/public/management/cypress/cypress_base.config.ts
index e7624ed670f50..7416ef60eda19 100644
--- a/x-pack/plugins/security_solution/public/management/cypress/cypress_base.config.ts
+++ b/x-pack/plugins/security_solution/public/management/cypress/cypress_base.config.ts
@@ -59,6 +59,8 @@ export const getCypressBaseConfig = (
         supportFile: 'public/management/cypress/support/e2e.ts',
         specPattern: 'public/management/cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
         experimentalRunAllSpecs: true,
+        experimentalMemoryManagement: true,
+        experimentalInteractiveRunEvents: true,
         setupNodeEvents: (on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) => {
           dataLoaders(on, config);
 

From 911ed802b736e012cf0cd91677f2c8d2ef70567c Mon Sep 17 00:00:00 2001
From: Marco Liberati <dej611@users.noreply.github.com>
Date: Thu, 28 Sep 2023 15:19:23 +0200
Subject: [PATCH 18/20] [Lens] Mute visualization modifier badge if layer has
 only manual annotations (#167483)

## Summary

Fixes #167408

This PR reduces the amount of "visualization modifiers" in the specific
case where ALL annotations in a layer are manual.
The `Ignore global filters` will still be shown if at least one
query-based annotation is defined in the layer.


### Checklist

Delete any items that are not applicable to this PR.

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: Stratoula Kalafateli <efstratia.kalafateli@elastic.co>
---
 .../visualizations/xy/visualization.test.tsx  | 88 ++++++++++++++++++-
 .../visualizations/xy/visualization.tsx       |  5 +-
 .../apps/lens/group2/layer_actions.ts         | 16 ++++
 3 files changed, 104 insertions(+), 5 deletions(-)

diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx
index c9d52d43df7ef..9024e43292939 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx
@@ -731,12 +731,14 @@ describe('xy_visualization', () => {
           },
         };
       });
+
       it('when there is no date histogram annotation layer is disabled', () => {
         const supportedAnnotationLayer = xyVisualization
           .getSupportedLayers(exampleState())
           .find((a) => a.type === 'annotations');
         expect(supportedAnnotationLayer?.disabled).toBeTruthy();
       });
+
       it('for data with date histogram annotation layer is enabled and calculates initial dimensions', () => {
         const supportedAnnotationLayer = xyVisualization
           .getSupportedLayers(exampleState(), frame)
@@ -3216,6 +3218,34 @@ describe('xy_visualization', () => {
     });
 
     describe('info', () => {
+      function createStateWithAnnotationProps(annotation: Partial<EventAnnotationConfig>) {
+        return {
+          layers: [
+            {
+              layerId: 'first',
+              layerType: layerTypes.DATA,
+              seriesType: 'area',
+              splitAccessor: undefined,
+              xAccessor: DATE_HISTORGRAM_COLUMN_ID,
+              accessors: ['b'],
+            },
+            {
+              layerId: 'layerId',
+              layerType: 'annotations',
+              indexPatternId: 'first',
+              annotations: [
+                {
+                  label: 'Event',
+                  id: '1',
+                  type: 'query',
+                  timeField: 'start_date',
+                  ...annotation,
+                },
+              ],
+            },
+          ],
+        } as XYState;
+      }
       function getFrameMock() {
         const datasourceMock = createMockDatasource('testDatasource');
         datasourceMock.publicAPIMock.getOperationForColumnId.mockImplementation((id) =>
@@ -3239,21 +3269,47 @@ describe('xy_visualization', () => {
         });
       }
 
-      it('should return an info message if annotation layer is ignoring the global filters', () => {
-        const initialState = exampleState();
+      it('should not return an info message if annotation layer is ignoring the global filters but contains only manual annotations', () => {
+        const initialState = createStateWithAnnotationProps({});
         const state: State = {
           ...initialState,
           layers: [
-            ...initialState.layers,
+            // replace the existing annotation layers with a new one
+            ...initialState.layers.filter(({ layerType }) => layerType !== layerTypes.ANNOTATIONS),
             {
               layerId: 'annotation',
               layerType: layerTypes.ANNOTATIONS,
-              annotations: [exampleAnnotation2],
+              annotations: [exampleAnnotation2, { ...exampleAnnotation2, id: 'an3' }],
               ignoreGlobalFilters: true,
               indexPatternId: 'myIndexPattern',
             },
           ],
         };
+        expect(xyVisualization.getUserMessages!(state, { frame: getFrameMock() })).toHaveLength(0);
+      });
+
+      it("should return an info message if the annotation layer is ignoring filters and there's at least a query annotation", () => {
+        const state = createStateWithAnnotationProps({
+          filter: {
+            language: 'kuery',
+            query: 'agent.keyword: *',
+            type: 'kibana_query',
+          },
+          id: 'newColId',
+          key: {
+            type: 'point_in_time',
+          },
+          label: 'agent.keyword: *',
+          timeField: 'timestamp',
+          type: 'query',
+        });
+
+        const annotationLayer = state.layers.find(
+          ({ layerType }) => layerType === layerTypes.ANNOTATIONS
+        )! as XYAnnotationLayerConfig;
+        annotationLayer.ignoreGlobalFilters = true;
+        annotationLayer.annotations.push(exampleAnnotation2);
+
         expect(xyVisualization.getUserMessages!(state, { frame: getFrameMock() })).toContainEqual(
           expect.objectContaining({
             displayLocations: [{ id: 'embeddableBadge' }],
@@ -3264,6 +3320,30 @@ describe('xy_visualization', () => {
           })
         );
       });
+
+      it('should not return an info message if annotation layer is not ignoring the global filters', () => {
+        const state = createStateWithAnnotationProps({
+          filter: {
+            language: 'kuery',
+            query: 'agent.keyword: *',
+            type: 'kibana_query',
+          },
+          id: 'newColId',
+          key: {
+            type: 'point_in_time',
+          },
+          label: 'agent.keyword: *',
+          timeField: 'timestamp',
+          type: 'query',
+        });
+
+        const annotationLayer = state.layers.find(
+          ({ layerType }) => layerType === layerTypes.ANNOTATIONS
+        )! as XYAnnotationLayerConfig;
+        annotationLayer.ignoreGlobalFilters = false;
+        annotationLayer.annotations.push(exampleAnnotation2);
+        expect(xyVisualization.getUserMessages!(state, { frame: getFrameMock() })).toHaveLength(0);
+      });
     });
   });
 
diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx
index 812d74ddcb331..07ebec4c47b58 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx
@@ -1149,7 +1149,10 @@ function getNotifiableFeatures(
   fieldFormats: FieldFormatsStart
 ): UserMessage[] {
   const annotationsWithIgnoreFlag = getAnnotationsLayers(state.layers).filter(
-    (layer) => layer.ignoreGlobalFilters
+    (layer) =>
+      layer.ignoreGlobalFilters &&
+      // If all annotations are manual, do not report it
+      layer.annotations.some((annotation) => annotation.type !== 'manual')
   );
   if (!annotationsWithIgnoreFlag.length) {
     return [];
diff --git a/x-pack/test/functional/apps/lens/group2/layer_actions.ts b/x-pack/test/functional/apps/lens/group2/layer_actions.ts
index 1afda462402c3..77d36603c0252 100644
--- a/x-pack/test/functional/apps/lens/group2/layer_actions.ts
+++ b/x-pack/test/functional/apps/lens/group2/layer_actions.ts
@@ -12,6 +12,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
   const PageObjects = getPageObjects(['visualize', 'lens', 'common', 'header']);
   const find = getService('find');
   const testSubjects = getService('testSubjects');
+  const retry = getService('retry');
 
   describe('lens layer actions tests', () => {
     it('should allow creation of lens xy chart', async () => {
@@ -239,6 +240,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
       // by default annotations ignore global filters
       await PageObjects.lens.createLayer('annotations');
 
+      await testSubjects.click('lns-layerPanel-2 > lnsXY_xAnnotationsPanel > lns-dimensionTrigger');
+
+      await testSubjects.click('lnsXY_annotation_query');
+
+      await retry.try(async () => {
+        if (!(await testSubjects.exists('annotation-query-based-query-input'))) {
+          await testSubjects.setValue('annotation-query-based-query-input', '*', {
+            clearWithKeyboard: true,
+            typeCharByChar: true,
+          });
+        }
+      });
+
+      await PageObjects.lens.closeDimensionEditor();
+
       await PageObjects.lens.save('sampledVisualization', false, true, false, 'new');
 
       // now check for the bottom-left badge

From c2745d3c19291c8ceabe7830dad2922161fa55b5 Mon Sep 17 00:00:00 2001
From: Kevin Delemme <kevin.delemme@elastic.co>
Date: Thu, 28 Sep 2023 09:20:33 -0400
Subject: [PATCH 19/20] feat(slo): limit perPage in find api (#167185)

---
 .../observability/docs/openapi/slo/bundled.json     |  3 ++-
 .../observability/docs/openapi/slo/bundled.yaml     |  1 +
 .../openapi/slo/paths/s@{spaceid}@api@slos.yaml     |  1 +
 .../server/services/slo/find_slo.test.ts            | 13 +++++++++++++
 .../observability/server/services/slo/find_slo.ts   |  6 ++++++
 5 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/x-pack/plugins/observability/docs/openapi/slo/bundled.json b/x-pack/plugins/observability/docs/openapi/slo/bundled.json
index 559f5713e2c35..e51f3828886cf 100644
--- a/x-pack/plugins/observability/docs/openapi/slo/bundled.json
+++ b/x-pack/plugins/observability/docs/openapi/slo/bundled.json
@@ -160,7 +160,8 @@
             "description": "The number of SLOs to return per page",
             "schema": {
               "type": "integer",
-              "default": 25
+              "default": 25,
+              "maximum": 5000
             },
             "example": 25
           },
diff --git a/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml b/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml
index efeeb090f0156..4b0ca84bc7c52 100644
--- a/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml
+++ b/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml
@@ -99,6 +99,7 @@ paths:
           schema:
             type: integer
             default: 25
+            maximum: 5000
           example: 25
         - name: sortBy
           in: query
diff --git a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos.yaml b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos.yaml
index 0c7559e41bb62..b606a0aac05fb 100644
--- a/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos.yaml
+++ b/x-pack/plugins/observability/docs/openapi/slo/paths/s@{spaceid}@api@slos.yaml
@@ -79,6 +79,7 @@ get:
       schema:
         type: integer
         default: 25
+        maximum: 5000
       example: 25
     - name: sortBy
       in: query
diff --git a/x-pack/plugins/observability/server/services/slo/find_slo.test.ts b/x-pack/plugins/observability/server/services/slo/find_slo.test.ts
index c5b9d2b73d202..10436bc0fad54 100644
--- a/x-pack/plugins/observability/server/services/slo/find_slo.test.ts
+++ b/x-pack/plugins/observability/server/services/slo/find_slo.test.ts
@@ -138,6 +138,19 @@ describe('FindSLO', () => {
       `);
     });
   });
+
+  describe('validation', () => {
+    it("throws an error when 'perPage > 5000'", async () => {
+      const slo = createSLO();
+      mockSummarySearchClient.search.mockResolvedValueOnce(summarySearchResult(slo));
+      mockRepository.findAllByIds.mockResolvedValueOnce([slo]);
+
+      await expect(findSLO.execute({ perPage: '5000' })).resolves.not.toThrow();
+      await expect(findSLO.execute({ perPage: '5001' })).rejects.toThrowError(
+        'perPage limit to 5000'
+      );
+    });
+  });
 });
 
 function summarySearchResult(slo: SLO): Paginated<SLOSummary> {
diff --git a/x-pack/plugins/observability/server/services/slo/find_slo.ts b/x-pack/plugins/observability/server/services/slo/find_slo.ts
index b2b5bab8ee75a..cf8150db3e627 100644
--- a/x-pack/plugins/observability/server/services/slo/find_slo.ts
+++ b/x-pack/plugins/observability/server/services/slo/find_slo.ts
@@ -7,11 +7,13 @@
 
 import { FindSLOParams, FindSLOResponse, findSLOResponseSchema } from '@kbn/slo-schema';
 import { SLO, SLOWithSummary } from '../../domain/models';
+import { IllegalArgumentError } from '../../errors';
 import { SLORepository } from './slo_repository';
 import { Pagination, SLOSummary, Sort, SummarySearchClient } from './summary_search_client';
 
 const DEFAULT_PAGE = 1;
 const DEFAULT_PER_PAGE = 25;
+const MAX_PER_PAGE = 5000;
 
 export class FindSLO {
   constructor(
@@ -52,6 +54,10 @@ function toPagination(params: FindSLOParams): Pagination {
   const page = Number(params.page);
   const perPage = Number(params.perPage);
 
+  if (!isNaN(perPage) && perPage > MAX_PER_PAGE) {
+    throw new IllegalArgumentError('perPage limit to 5000');
+  }
+
   return {
     page: !isNaN(page) && page >= 1 ? page : DEFAULT_PAGE,
     perPage: !isNaN(perPage) && perPage >= 1 ? perPage : DEFAULT_PER_PAGE,

From 67cc63affe0da782f80472c53d3f259637568012 Mon Sep 17 00:00:00 2001
From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Date: Thu, 28 Sep 2023 09:26:55 -0400
Subject: [PATCH 20/20] skip failing test suite (#167496)