diff --git a/src/platform/packages/shared/kbn-lens-embeddable-utils/config_builder/schema/charts/xy.ts b/src/platform/packages/shared/kbn-lens-embeddable-utils/config_builder/schema/charts/xy.ts index 772179dee7359..c037907eaed72 100644 --- a/src/platform/packages/shared/kbn-lens-embeddable-utils/config_builder/schema/charts/xy.ts +++ b/src/platform/packages/shared/kbn-lens-embeddable-utils/config_builder/schema/charts/xy.ts @@ -410,7 +410,14 @@ const xyDataLayerSchemaESQL = schema.object( ...layerSettingsSchema, ...datasetEsqlTableSchema, ...xyDataLayerSharedSchema, - breakdown_by: schema.maybe(esqlColumnSchema), + breakdown_by: schema.maybe( + esqlColumnSchema.extends( + { + color: schema.maybe(colorMappingSchema), + }, + { meta: { description: 'ES|QL column for breakdown with optional color mapping' } } + ) + ), y: schema.arrayOf( esqlColumnSchema.extends( { diff --git a/src/platform/packages/shared/kbn-lens-embeddable-utils/config_builder/tests/xy/esqlXY.mock.ts b/src/platform/packages/shared/kbn-lens-embeddable-utils/config_builder/tests/xy/esqlXY.mock.ts index 6dd5fd93dd649..ae269dd10839e 100644 --- a/src/platform/packages/shared/kbn-lens-embeddable-utils/config_builder/tests/xy/esqlXY.mock.ts +++ b/src/platform/packages/shared/kbn-lens-embeddable-utils/config_builder/tests/xy/esqlXY.mock.ts @@ -7,8 +7,11 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +import type { XYDataLayerConfig } from '@kbn/lens-common'; import type { LensAttributes } from '../../types'; +const LAYER_ID = 'c2eacea8-91d4-4372-a82d-8760979ff893'; + export const esqlChart: LensAttributes = { title: 'Bar vertical stacked', references: [], @@ -129,3 +132,69 @@ export const esqlChart: LensAttributes = { visualizationType: 'lnsXY', version: 2, }; + +/** + * Derives from esqlChart, adding a breakdown column (category) with colorMapping assignments + */ +export const esqlChartWithBreakdownColorMapping: LensAttributes = { + ...esqlChart, + title: 'ES|QL bar with breakdown color mapping', + state: { + ...esqlChart.state, + datasourceStates: { + textBased: { + layers: { + [LAYER_ID]: { + ...esqlChart.state.datasourceStates.textBased!.layers[LAYER_ID], + query: { + esql: 'FROM kibana_sample_data_ecommerce \n| EVAL buckets = DATE_TRUNC(12 hours, order_date) | STATS count = COUNT(*) BY buckets, category', + }, + columns: [ + ...esqlChart.state.datasourceStates.textBased!.layers[LAYER_ID].columns, + { + columnId: 'category', + fieldName: 'category', + label: 'category', + customLabel: false, + meta: { + type: 'string', + esType: 'keyword', + }, + }, + ], + }, + }, + }, + }, + query: { + esql: 'FROM kibana_sample_data_ecommerce \n| EVAL buckets = DATE_TRUNC(12 hours, order_date) | STATS count = COUNT(*) BY buckets, category', + }, + visualization: { + ...(esqlChart.state.visualization as Record), + layers: [ + { + ...(esqlChart.state.visualization as { layers: XYDataLayerConfig[] }).layers[0], + splitAccessors: ['category'], + colorMapping: { + assignments: [ + { + rules: [{ type: 'raw', value: 'Clothing' }], + color: { type: 'colorCode', colorCode: '#ff0000' }, + touched: false, + }, + ], + specialAssignments: [ + { + rules: [{ type: 'other' }], + color: { type: 'loop' }, + touched: false, + }, + ], + paletteId: 'default', + colorMode: { type: 'categorical' }, + }, + }, + ], + }, + }, +}; diff --git a/src/platform/packages/shared/kbn-lens-embeddable-utils/config_builder/tests/xy/xy.test.ts b/src/platform/packages/shared/kbn-lens-embeddable-utils/config_builder/tests/xy/xy.test.ts index 5ad284177d06c..b0fc27fc386a3 100644 --- a/src/platform/packages/shared/kbn-lens-embeddable-utils/config_builder/tests/xy/xy.test.ts +++ b/src/platform/packages/shared/kbn-lens-embeddable-utils/config_builder/tests/xy/xy.test.ts @@ -24,7 +24,7 @@ import { } from './basicXY.mock'; import { dualReferenceLineXY, referenceLineXY } from './referenceLines.mock'; import { annotationXY } from './annotations.mock'; -import { esqlChart } from './esqlXY.mock'; +import { esqlChart, esqlChartWithBreakdownColorMapping } from './esqlXY.mock'; function setSeriesType(attributes: LensAttributes, seriesType: 'bar' | 'line' | 'area') { return { @@ -110,6 +110,12 @@ describe('XY', () => { validateConverter(setSeriesType(esqlChart, type), xyStateSchema); }); } + + for (const type of ['bar', 'line', 'area'] as const) { + it(`should work for an ES|QL ${type} chart with breakdown and color mapping`, () => { + validateConverter(setSeriesType(esqlChartWithBreakdownColorMapping, type), xyStateSchema); + }); + } }); }); describe('API => Lens State SO format', () => { @@ -173,6 +179,46 @@ describe('XY', () => { ); }); + it.each(anyType)( + 'should work for ES|QL mode for %s chart with breakdown and categorical color mapping', + (type) => { + validateAPIConverter( + { + type: 'xy', + title: `${type} Chart with Color Mapping`, + layers: [ + { + dataset: { + type: 'esql', + query: + 'FROM kibana_sample_data | STATS count = count() BY category, buckets = BUCKET(3 hours, order_date)', + }, + type, + ignore_global_filters: false, + sampling: 1, + x: { operation: 'value', column: 'buckets' }, + y: [{ operation: 'value', column: 'count' }], + breakdown_by: { + operation: 'value', + column: 'category', + color: { + mode: 'categorical', + palette: 'default', + mapping: [ + { + values: ['Clothing'], + color: { type: 'colorCode', value: '#ff0000' }, + }, + ], + }, + }, + }, + ], + }, + xyStateSchema + ); + } + ); it.each(anyType.map((type) => anyType.map((anotherType) => [type, anotherType])).flat(1))( 'should handle multiple metric in multiple layers %s + %s with reference lines and annotations with mixed datasets', (type1, type2) => { diff --git a/src/platform/packages/shared/kbn-lens-embeddable-utils/config_builder/transforms/charts/xy/api_layers.ts b/src/platform/packages/shared/kbn-lens-embeddable-utils/config_builder/transforms/charts/xy/api_layers.ts index 4085fc76cf1c0..f1fb6459489bd 100644 --- a/src/platform/packages/shared/kbn-lens-embeddable-utils/config_builder/transforms/charts/xy/api_layers.ts +++ b/src/platform/packages/shared/kbn-lens-embeddable-utils/config_builder/transforms/charts/xy/api_layers.ts @@ -150,7 +150,12 @@ function convertDataLayerToAPI( y: y || [], ...(breakdown_by ? { - breakdown_by, + breakdown_by: { + ...breakdown_by, + ...(visualization.colorMapping + ? { color: fromColorMappingLensStateToAPI(visualization.colorMapping) } + : {}), + }, } : {}), };