diff --git a/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/__snapshots__/heatmap_function.test.ts.snap b/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/__snapshots__/heatmap_function.test.ts.snap index 96b70e33021f4..1b644ef0a4938 100644 --- a/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/__snapshots__/heatmap_function.test.ts.snap +++ b/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/__snapshots__/heatmap_function.test.ts.snap @@ -77,6 +77,7 @@ Object { "xAccessor": "col-1-2", "yAccessor": undefined, }, + "canNavigateToLens": false, "data": Object { "columns": Array [ Object { diff --git a/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/heatmap_function.ts b/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/heatmap_function.ts index 548d4ec0ab49e..f0c309de19236 100644 --- a/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/heatmap_function.ts +++ b/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/heatmap_function.ts @@ -232,6 +232,7 @@ export const heatmapFunction = (): HeatmapExpressionFunctionDefinition => ({ }, syncTooltips: handlers?.isSyncTooltipsEnabled?.() ?? false, syncCursor: handlers?.isSyncCursorEnabled?.() ?? true, + canNavigateToLens: Boolean(handlers?.variables?.canNavigateToLens), }, }; }, diff --git a/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts index 5aa1507f30b03..1bf5fe3bbb36b 100644 --- a/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts +++ b/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts @@ -94,6 +94,7 @@ export interface HeatmapExpressionProps { args: HeatmapArguments; syncTooltips: boolean; syncCursor: boolean; + canNavigateToLens?: boolean; } export interface HeatmapRender { diff --git a/src/plugins/chart_expressions/expression_heatmap/public/expression_renderers/heatmap_renderer.tsx b/src/plugins/chart_expressions/expression_heatmap/public/expression_renderers/heatmap_renderer.tsx index 4b813fb93416f..b14ee1382deb2 100644 --- a/src/plugins/chart_expressions/expression_heatmap/public/expression_renderers/heatmap_renderer.tsx +++ b/src/plugins/chart_expressions/expression_heatmap/public/expression_renderers/heatmap_renderer.tsx @@ -61,9 +61,14 @@ export const heatmapRenderer: ( const visualizationType = extractVisualizationType(executionContext); if (containerType && visualizationType) { - plugins.usageCollection?.reportUiCounter(containerType, METRIC_TYPE.COUNT, [ + const events = [ `render_${visualizationType}_${EXPRESSION_HEATMAP_NAME}`, - ]); + config.canNavigateToLens + ? `render_${visualizationType}_${EXPRESSION_HEATMAP_NAME}_convertable` + : undefined, + ].filter((event): event is string => Boolean(event)); + + plugins.usageCollection?.reportUiCounter(containerType, METRIC_TYPE.COUNT, events); } handlers.done(); diff --git a/src/plugins/vis_types/heatmap/kibana.json b/src/plugins/vis_types/heatmap/kibana.json index c8df98e2b343a..b7f4a3bacbb90 100644 --- a/src/plugins/vis_types/heatmap/kibana.json +++ b/src/plugins/vis_types/heatmap/kibana.json @@ -1,14 +1,27 @@ { - "id": "visTypeHeatmap", - "version": "kibana", - "ui": true, - "server": true, - "requiredPlugins": ["charts", "data", "expressions", "visualizations", "usageCollection", "fieldFormats"], - "requiredBundles": ["visDefaultEditor"], - "extraPublicDirs": ["common/index"], - "owner": { - "name": "Vis Editors", - "githubTeam": "kibana-vis-editors" - }, - "description": "Contains the heatmap implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy heatmap charts library advanced setting." - } + "id": "visTypeHeatmap", + "version": "kibana", + "ui": true, + "server": true, + "requiredPlugins": [ + "charts", + "data", + "expressions", + "visualizations", + "usageCollection", + "fieldFormats", + "dataViews" + ], + "requiredBundles": [ + "visDefaultEditor", + "kibanaUtils" + ], + "extraPublicDirs": [ + "common/index" + ], + "owner": { + "name": "Vis Editors", + "githubTeam": "kibana-vis-editors" + }, + "description": "Contains the heatmap implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy heatmap charts library advanced setting." +} \ No newline at end of file diff --git a/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/index.test.ts b/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/index.test.ts new file mode 100644 index 0000000000000..3f60b6fde0a94 --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/index.test.ts @@ -0,0 +1,114 @@ +/* + * 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 { AvgColumn, DateHistogramColumn } from '@kbn/visualizations-plugin/common/convert_to_lens'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { getConfiguration } from '.'; +import { sampleHeatmapVis } from '../../sample_vis.test.mocks'; +import { HeatmapVisParams } from '../../types'; + +describe('getConfiguration', () => { + const layerId = 'layer-id'; + let vis: Vis; + + const metric: AvgColumn = { + sourceField: 'price', + columnId: 'column-1', + operationType: 'average', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: {}, + }; + const xColumn: DateHistogramColumn = { + sourceField: 'price', + columnId: 'column-2', + operationType: 'date_histogram', + isBucketed: true, + isSplit: false, + dataType: 'string', + params: { + interval: '1h', + }, + }; + + const yColumn: DateHistogramColumn = { + sourceField: 'price', + columnId: 'column-3', + operationType: 'date_histogram', + isBucketed: true, + isSplit: true, + dataType: 'string', + params: { + interval: '1h', + }, + }; + + beforeEach(() => { + vis = sampleHeatmapVis as unknown as Vis; + }); + + test('should return valid configuration', async () => { + const result = await getConfiguration(layerId, vis, { + metrics: [metric.columnId], + buckets: [xColumn.columnId, yColumn.columnId], + }); + expect(result).toEqual({ + gridConfig: { + isCellLabelVisible: true, + isXAxisLabelVisible: true, + isXAxisTitleVisible: true, + isYAxisLabelVisible: true, + isYAxisTitleVisible: true, + type: 'heatmap_grid', + }, + layerId, + layerType: 'data', + legend: { isVisible: undefined, position: 'right', type: 'heatmap_legend' }, + palette: { + accessor: 'column-1', + name: 'custom', + params: { + colorStops: [ + { color: '#F7FBFF', stop: 0 }, + { color: '#DEEBF7', stop: 12.5 }, + { color: '#C3DBEE', stop: 25 }, + { color: '#9CC8E2', stop: 37.5 }, + { color: '#6DAED5', stop: 50 }, + { color: '#4391C6', stop: 62.5 }, + { color: '#2271B3', stop: 75 }, + { color: '#0D5097', stop: 87.5 }, + ], + continuity: 'none', + maxSteps: 5, + name: 'custom', + progression: 'fixed', + rangeMax: 100, + rangeMin: 0, + rangeType: 'number', + reverse: false, + stops: [ + { color: '#F7FBFF', stop: 12.5 }, + { color: '#DEEBF7', stop: 25 }, + { color: '#C3DBEE', stop: 37.5 }, + { color: '#9CC8E2', stop: 50 }, + { color: '#6DAED5', stop: 62.5 }, + { color: '#4391C6', stop: 75 }, + { color: '#2271B3', stop: 87.5 }, + { color: '#0D5097', stop: 100 }, + ], + }, + type: 'palette', + }, + shape: 'heatmap', + valueAccessor: metric.columnId, + xAccessor: xColumn.columnId, + yAccessor: yColumn.columnId, + }); + }); +}); diff --git a/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/index.ts new file mode 100644 index 0000000000000..2e7a3f161514a --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/index.ts @@ -0,0 +1,54 @@ +/* + * 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 { HeatmapConfiguration } from '@kbn/visualizations-plugin/common'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { HeatmapVisParams } from '../../types'; +import { getPaletteForHeatmap } from './palette'; + +export const getConfiguration = async ( + layerId: string, + vis: Vis, + { + metrics, + buckets, + }: { + metrics: string[]; + buckets: string[]; + } +): Promise => { + const [valueAccessor] = metrics; + const [xAccessor, yAccessor] = buckets; + + const { params, uiState } = vis; + const state = uiState.get('vis', {}) ?? {}; + + const palette = await getPaletteForHeatmap(params); + return { + layerId, + layerType: 'data', + shape: 'heatmap', + legend: { + type: 'heatmap_legend', + isVisible: state.legendOpen, + position: params.legendPosition, + }, + gridConfig: { + type: 'heatmap_grid', + isCellLabelVisible: params.valueAxes?.[0].labels.show ?? false, + isXAxisLabelVisible: true, + isYAxisLabelVisible: true, + isYAxisTitleVisible: true, + isXAxisTitleVisible: true, + }, + valueAccessor, + xAccessor, + yAccessor, + palette: palette ? { ...palette, accessor: valueAccessor } : undefined, + }; +}; diff --git a/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/palette.ts b/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/palette.ts new file mode 100644 index 0000000000000..32187e184d4ef --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/palette.ts @@ -0,0 +1,52 @@ +/* + * 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 { Range } from '@kbn/expressions-plugin/common'; +import { convertToLensModule } from '@kbn/visualizations-plugin/public'; +import { HeatmapVisParams } from '../../types'; +import { getStopsWithColorsFromColorsNumber } from '../../utils/palette'; + +type HeatmapVisParamsWithRanges = Omit & { + colorsRange: Exclude; +}; + +const isHeatmapVisParamsWithRanges = ( + params: HeatmapVisParams | HeatmapVisParamsWithRanges +): params is HeatmapVisParamsWithRanges => { + return Boolean(params.setColorRange && params.colorsRange && params.colorsRange.length); +}; + +export const getPaletteForHeatmap = async (params: HeatmapVisParams) => { + const { getPalette, getPaletteFromStopsWithColors, getPercentageModeConfig } = + await convertToLensModule; + + if (isHeatmapVisParamsWithRanges(params)) { + const percentageModeConfig = getPercentageModeConfig(params, false); + return getPalette(params, percentageModeConfig, params.percentageMode); + } + + const { color, stop = [] } = getStopsWithColorsFromColorsNumber( + params.colorsNumber, + params.colorSchema, + params.invertColors, + true + ); + const colorsRange: Range[] = [{ from: stop[0], to: stop[stop.length - 1], type: 'range' }]; + const { colorSchema, invertColors, percentageMode } = params; + const percentageModeConfig = getPercentageModeConfig( + { + colorsRange, + colorSchema, + invertColors, + percentageMode, + }, + false + ); + + return getPaletteFromStopsWithColors({ color, stop: stop ?? [] }, percentageModeConfig, true); +}; diff --git a/src/plugins/vis_types/heatmap/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/heatmap/public/convert_to_lens/index.test.ts new file mode 100644 index 0000000000000..ef86b3829c248 --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/convert_to_lens/index.test.ts @@ -0,0 +1,166 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { ColorSchemas } from '@kbn/charts-plugin/common'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { convertToLens } from '.'; +import { HeatmapVisParams } from '../types'; + +const mockGetColumnsFromVis = jest.fn(); +const mockGetConfiguration = jest.fn().mockReturnValue({}); +const mockGetDataViewByIndexPatternId = jest.fn(); +const mockConvertToFiltersColumn = jest.fn(); + +jest.mock('../services', () => ({ + getDataViewsStart: jest.fn(() => ({ get: () => ({}), getDefault: () => ({}) })), +})); + +jest.mock('@kbn/visualizations-plugin/public', () => ({ + convertToLensModule: Promise.resolve({ + getColumnsFromVis: jest.fn(() => mockGetColumnsFromVis()), + convertToFiltersColumn: jest.fn(() => mockConvertToFiltersColumn()), + }), + getDataViewByIndexPatternId: jest.fn(() => mockGetDataViewByIndexPatternId()), +})); + +jest.mock('./configurations', () => ({ + getConfiguration: jest.fn(() => mockGetConfiguration()), +})); + +const params: HeatmapVisParams = { + addTooltip: false, + addLegend: false, + enableHover: true, + legendPosition: 'bottom', + lastRangeIsRightOpen: false, + percentageMode: false, + valueAxes: [], + colorSchema: ColorSchemas.Blues, + invertColors: false, + colorsNumber: 4, + setColorRange: true, +}; + +const vis = { + isHierarchical: () => false, + type: {}, + params, + data: {}, +} as unknown as Vis; + +const timefilter = { + getAbsoluteTime: () => {}, +} as any; + +describe('convertToLens', () => { + beforeEach(() => { + mockGetDataViewByIndexPatternId.mockReturnValue({ id: 'index-pattern' }); + mockConvertToFiltersColumn.mockReturnValue({ columnId: 'column-id-1' }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should return null if timefilter is undefined', async () => { + const result = await convertToLens(vis); + expect(result).toBeNull(); + }); + + test('should return null if mockGetDataViewByIndexPatternId returns null', async () => { + mockGetDataViewByIndexPatternId.mockReturnValue(null); + const result = await convertToLens(vis, timefilter); + expect(mockGetDataViewByIndexPatternId).toBeCalledTimes(1); + expect(mockGetColumnsFromVis).toBeCalledTimes(0); + expect(result).toBeNull(); + }); + + test('should return null if getColumnsFromVis returns null', async () => { + mockGetColumnsFromVis.mockReturnValue(null); + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should return null if metrics count is more than 1', async () => { + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1', '2'], + buckets: { all: [] }, + columns: [{ columnId: '2' }, { columnId: '1' }], + }, + ]); + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should return empty filters for x-axis if no buckets are specified', async () => { + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1'], + buckets: { all: [] }, + columns: [{ columnId: '1', dataType: 'number' }], + columnsWithoutReferenced: [ + { columnId: '1', meta: { aggId: 'agg-1' } }, + { columnId: '2', meta: { aggId: 'agg-2' } }, + { columnId: 'column-id-1' }, + ], + }, + ]); + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toEqual( + expect.objectContaining({ + configuration: {}, + indexPatternIds: ['index-pattern'], + layers: [ + expect.objectContaining({ + columnOrder: [], + columns: [{ columnId: '1', dataType: 'number' }, { columnId: 'column-id-1' }], + indexPatternId: 'index-pattern', + }), + ], + type: 'lnsHeatmap', + }) + ); + }); + + test('should return correct state for valid vis', async () => { + const config = { + layerType: 'data', + }; + + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1'], + buckets: { all: ['2'] }, + columns: [{ columnId: '1', dataType: 'number' }], + columnsWithoutReferenced: [ + { columnId: '1', meta: { aggId: 'agg-1' } }, + { columnId: '2', meta: { aggId: 'agg-2' } }, + ], + }, + ]); + mockGetConfiguration.mockReturnValue(config); + + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(mockGetConfiguration).toBeCalledTimes(1); + expect(result?.type).toEqual('lnsHeatmap'); + expect(result?.layers.length).toEqual(1); + expect(result?.layers[0]).toEqual( + expect.objectContaining({ + columnOrder: [], + columns: [{ columnId: '1', dataType: 'number' }, { columnId: 'column-id-1' }], + indexPatternId: 'index-pattern', + }) + ); + expect(result?.configuration).toEqual(config); + }); +}); diff --git a/src/plugins/vis_types/heatmap/public/convert_to_lens/index.ts b/src/plugins/vis_types/heatmap/public/convert_to_lens/index.ts new file mode 100644 index 0000000000000..546d497e80560 --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/convert_to_lens/index.ts @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Column, ColumnWithMeta } from '@kbn/visualizations-plugin/common'; +import { + convertToLensModule, + getDataViewByIndexPatternId, +} from '@kbn/visualizations-plugin/public'; +import uuid from 'uuid'; +import { getDataViewsStart } from '../services'; +import { getConfiguration } from './configurations'; +import { ConvertHeatmapToLensVisualization } from './types'; + +export const isColumnWithMeta = (column: Column): column is ColumnWithMeta => { + if ((column as ColumnWithMeta).meta) { + return true; + } + return false; +}; + +export const excludeMetaFromColumn = (column: Column) => { + if (isColumnWithMeta(column)) { + const { meta, ...rest } = column; + return rest; + } + return column; +}; + +export const convertToLens: ConvertHeatmapToLensVisualization = async (vis, timefilter) => { + if (!timefilter) { + return null; + } + + const dataViews = getDataViewsStart(); + const dataView = await getDataViewByIndexPatternId(vis.data.indexPattern?.id, dataViews); + + if (!dataView) { + return null; + } + + const { getColumnsFromVis, convertToFiltersColumn } = await convertToLensModule; + const layers = getColumnsFromVis(vis, timefilter, dataView, { + buckets: ['segment'], + splits: ['group'], + unsupported: ['split_row', 'split_column'], + }); + + if (layers === null) { + return null; + } + + const [layerConfig] = layers; + + const xColumn = layerConfig.columns.find(({ isBucketed, isSplit }) => isBucketed && !isSplit); + const xAxisColumn = + xColumn ?? + convertToFiltersColumn(uuid(), { filters: [{ input: { language: 'lucene', query: '*' } }] })!; + + if (xColumn?.columnId !== xAxisColumn?.columnId) { + layerConfig.buckets.all.push(xAxisColumn.columnId); + layerConfig.columns.push(xAxisColumn); + } + const yColumn = layerConfig.columns.find(({ isBucketed, isSplit }) => isBucketed && isSplit); + + if (!layerConfig.buckets.all.length || layerConfig.metrics.length > 1) { + return null; + } + + const layerId = uuid(); + + const indexPatternId = dataView.id!; + const configuration = await getConfiguration(layerId, vis, { + metrics: layerConfig.metrics, + buckets: [xAxisColumn.columnId, yColumn?.columnId].filter((c): c is string => + Boolean(c) + ), + }); + + return { + type: 'lnsHeatmap', + layers: [ + { + indexPatternId, + layerId, + columns: layerConfig.columns.map(excludeMetaFromColumn), + columnOrder: [], + }, + ], + configuration, + indexPatternIds: [indexPatternId], + }; +}; diff --git a/src/plugins/vis_types/heatmap/public/convert_to_lens/types.ts b/src/plugins/vis_types/heatmap/public/convert_to_lens/types.ts new file mode 100644 index 0000000000000..732b977dd7b59 --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/convert_to_lens/types.ts @@ -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 { TimefilterContract } from '@kbn/data-plugin/public'; +import { NavigateToLensContext, HeatmapConfiguration } from '@kbn/visualizations-plugin/common'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { HeatmapVisParams } from '../types'; + +export type ConvertHeatmapToLensVisualization = ( + vis: Vis, + timefilter?: TimefilterContract +) => Promise | null>; diff --git a/src/plugins/vis_types/heatmap/public/plugin.ts b/src/plugins/vis_types/heatmap/public/plugin.ts index 44357cceaa86b..ee7349145e7c6 100644 --- a/src/plugins/vis_types/heatmap/public/plugin.ts +++ b/src/plugins/vis_types/heatmap/public/plugin.ts @@ -6,14 +6,16 @@ * Side Public License, v 1. */ -import { CoreSetup } from '@kbn/core/public'; +import { CoreSetup, CoreStart } from '@kbn/core/public'; import type { VisualizationsSetup } from '@kbn/visualizations-plugin/public'; import type { ChartsPluginSetup } from '@kbn/charts-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { LEGACY_HEATMAP_CHARTS_LIBRARY } from '../common'; import { heatmapVisType } from './vis_type'; +import { setDataViewsStart } from './services'; /** @internal */ export interface VisTypeHeatmapSetupDependencies { @@ -28,6 +30,11 @@ export interface VisTypeHeatmapPluginStartDependencies { fieldFormats: FieldFormatsStart; } +/** @internal */ +export interface VisTypeHeatmapStartDependencies { + dataViews: DataViewsPublicPluginStart; +} + export class VisTypeHeatmapPlugin { setup( core: CoreSetup, @@ -44,5 +51,7 @@ export class VisTypeHeatmapPlugin { return {}; } - start() {} + start(core: CoreStart, { dataViews }: VisTypeHeatmapStartDependencies) { + setDataViewsStart(dataViews); + } } diff --git a/src/plugins/vis_types/heatmap/public/sample_vis.test.mocks.ts b/src/plugins/vis_types/heatmap/public/sample_vis.test.mocks.ts index 6a33feb853221..89ede55b951ef 100644 --- a/src/plugins/vis_types/heatmap/public/sample_vis.test.mocks.ts +++ b/src/plugins/vis_types/heatmap/public/sample_vis.test.mocks.ts @@ -5,7 +5,9 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -export const sampleAreaVis = { + +const mockUiStateGet = jest.fn().mockReturnValue(() => {}); +export const sampleHeatmapVis = { type: { name: 'heatmap', title: 'Heatmap', @@ -1788,5 +1790,10 @@ export const sampleAreaVis = { }, }, isHierarchical: () => false, - uiState: {}, + uiState: { + vis: { + legendOpen: false, + }, + get: mockUiStateGet, + }, }; diff --git a/src/plugins/vis_types/heatmap/public/services.ts b/src/plugins/vis_types/heatmap/public/services.ts new file mode 100644 index 0000000000000..736ad70d49419 --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/services.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 { createGetterSetter } from '@kbn/kibana-utils-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; + +export const [getDataViewsStart, setDataViewsStart] = + createGetterSetter('dataViews'); diff --git a/src/plugins/vis_types/heatmap/public/to_ast.test.ts b/src/plugins/vis_types/heatmap/public/to_ast.test.ts index d1e312755cf49..07585d9f2332f 100644 --- a/src/plugins/vis_types/heatmap/public/to_ast.test.ts +++ b/src/plugins/vis_types/heatmap/public/to_ast.test.ts @@ -7,7 +7,7 @@ */ import { Vis } from '@kbn/visualizations-plugin/public'; -import { sampleAreaVis } from './sample_vis.test.mocks'; +import { sampleHeatmapVis } from './sample_vis.test.mocks'; import { buildExpression } from '@kbn/expressions-plugin/public'; import { toExpressionAst } from './to_ast'; @@ -33,7 +33,7 @@ describe('heatmap vis toExpressionAst function', () => { } as any; beforeEach(() => { - vis = sampleAreaVis as any; + vis = sampleHeatmapVis as any; }); it('should match basic snapshot', () => { diff --git a/src/plugins/vis_types/heatmap/public/utils/palette.ts b/src/plugins/vis_types/heatmap/public/utils/palette.ts index aa978a2954e90..29109a55fd1e7 100644 --- a/src/plugins/vis_types/heatmap/public/utils/palette.ts +++ b/src/plugins/vis_types/heatmap/public/utils/palette.ts @@ -27,13 +27,20 @@ const getColor = ( export const getStopsWithColorsFromColorsNumber = ( colorsNumber: number | '', colorSchema: ColorSchemas, - invertColors: boolean = false + invertColors: boolean = false, + includeZeroElement: boolean = false ) => { const colors = []; const stops = []; if (!colorsNumber) { return { color: [] }; } + + if (includeZeroElement) { + colors.push(TRANSPARENT); + stops.push(0); + } + const step = 100 / colorsNumber; for (let i = 0; i < colorsNumber; i++) { colors.push(getColor(i, colorsNumber, colorSchema, invertColors)); diff --git a/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx b/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx index e5a92ca03f5cc..336da6e2d8041 100644 --- a/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx +++ b/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx @@ -16,6 +16,7 @@ import { HeatmapTypeProps, HeatmapVisParams, AxisType, ScaleType } from '../type import { toExpressionAst } from '../to_ast'; import { getHeatmapOptions } from '../editor/components'; import { SplitTooltip } from './split_tooltip'; +import { convertToLens } from '../convert_to_lens'; export const getHeatmapVisTypeDefinition = ({ showElasticChartsOptions = false, @@ -154,4 +155,10 @@ export const getHeatmapVisTypeDefinition = ({ ], }, requiresSearch: true, + navigateToLens: async (vis, timefilter) => (vis ? convertToLens(vis, timefilter) : null), + getExpressionVariables: async (vis, timeFilter) => { + return { + canNavigateToLens: Boolean(vis?.params ? await convertToLens(vis, timeFilter) : null), + }; + }, }); diff --git a/src/plugins/vis_types/pie/kibana.json b/src/plugins/vis_types/pie/kibana.json index 4c5ee6b50579e..d9dca861e33be 100644 --- a/src/plugins/vis_types/pie/kibana.json +++ b/src/plugins/vis_types/pie/kibana.json @@ -1,14 +1,27 @@ { - "id": "visTypePie", - "version": "kibana", - "ui": true, - "server": true, - "requiredPlugins": ["charts", "data", "expressions", "visualizations", "usageCollection", "expressionPartitionVis", "dataViews"], - "requiredBundles": ["visDefaultEditor", "kibanaUtils"], - "extraPublicDirs": ["common/index"], - "owner": { - "name": "Vis Editors", - "githubTeam": "kibana-vis-editors" - }, - "description": "Contains the pie chart implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy charts library advanced setting." - } + "id": "visTypePie", + "version": "kibana", + "ui": true, + "server": true, + "requiredPlugins": [ + "charts", + "data", + "expressions", + "visualizations", + "usageCollection", + "expressionPartitionVis", + "dataViews" + ], + "requiredBundles": [ + "visDefaultEditor", + "kibanaUtils" + ], + "extraPublicDirs": [ + "common/index" + ], + "owner": { + "name": "Vis Editors", + "githubTeam": "kibana-vis-editors" + }, + "description": "Contains the pie chart implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy charts library advanced setting." +} \ No newline at end of file diff --git a/src/plugins/vis_types/table/public/convert_to_lens/index.ts b/src/plugins/vis_types/table/public/convert_to_lens/index.ts index e69faccbfd7ec..ed23d612cb68c 100644 --- a/src/plugins/vis_types/table/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/table/public/convert_to_lens/index.ts @@ -73,6 +73,7 @@ export const convertToLens: ConvertTableToLensVisualization = async (vis, timefi return null; } const percentageColumn = getPercentageColumnFormulaColumn({ + visType: vis.type.name, agg: metricAgg as SchemaConfig, dataView, aggs: visSchemas.metric as Array>, diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.test.ts index f0a8e4d32f7c3..02a6140625c07 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.test.ts @@ -8,7 +8,7 @@ import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { BUCKET_TYPES, METRIC_TYPES } from '@kbn/data-plugin/common'; -import { convertBucketToColumns } from '.'; +import { BucketAggs, convertBucketToColumns } from '.'; import { DateHistogramColumn, FiltersColumn, RangeColumn, TermsColumn } from '../../types'; import { AggBasedColumn, SchemaConfig } from '../../..'; @@ -27,7 +27,7 @@ jest.mock('../convert', () => ({ describe('convertBucketToColumns', () => { const field = stubLogstashDataView.fields[0].name; const dateField = stubLogstashDataView.fields.find((f) => f.type === 'date')!.name; - const bucketAggs: SchemaConfig[] = [ + const bucketAggs: Array> = [ { accessor: 0, label: '', @@ -152,6 +152,7 @@ describe('convertBucketToColumns', () => { }, }, ]; + const visType = 'heatmap'; afterEach(() => { jest.clearAllMocks(); @@ -167,7 +168,7 @@ describe('convertBucketToColumns', () => { >([ [ 'null if bucket agg type is not supported', - [{ dataView: stubLogstashDataView, agg: bucketAggs[6], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[6], aggs, metricColumns, visType }], () => {}, null, ], @@ -179,6 +180,7 @@ describe('convertBucketToColumns', () => { agg: { ...bucketAggs[0], aggParams: undefined }, aggs, metricColumns, + visType, }, ], () => {}, @@ -186,7 +188,7 @@ describe('convertBucketToColumns', () => { ], [ 'filters column if bucket agg is valid filters agg', - [{ dataView: stubLogstashDataView, agg: bucketAggs[0], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[0], aggs, metricColumns, visType }], () => { mockConvertToFiltersColumn.mockReturnValue({ operationType: 'filters', @@ -198,7 +200,7 @@ describe('convertBucketToColumns', () => { ], [ 'date histogram column if bucket agg is valid date histogram agg', - [{ dataView: stubLogstashDataView, agg: bucketAggs[1], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[1], aggs, metricColumns, visType }], () => { mockConvertToDateHistogramColumn.mockReturnValue({ operationType: 'date_histogram', @@ -210,7 +212,7 @@ describe('convertBucketToColumns', () => { ], [ 'date histogram column if bucket agg is valid terms agg with date field', - [{ dataView: stubLogstashDataView, agg: bucketAggs[3], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[3], aggs, metricColumns, visType }], () => { mockConvertToDateHistogramColumn.mockReturnValue({ operationType: 'date_histogram', @@ -222,7 +224,7 @@ describe('convertBucketToColumns', () => { ], [ 'terms column if bucket agg is valid terms agg with no date field', - [{ dataView: stubLogstashDataView, agg: bucketAggs[2], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[2], aggs, metricColumns, visType }], () => { mockConvertToTermsColumn.mockReturnValue({ operationType: 'terms', @@ -234,7 +236,7 @@ describe('convertBucketToColumns', () => { ], [ 'range column if bucket agg is valid histogram agg', - [{ dataView: stubLogstashDataView, agg: bucketAggs[4], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[4], aggs, metricColumns, visType }], () => { mockConvertToRangeColumn.mockReturnValue({ operationType: 'range', @@ -246,7 +248,7 @@ describe('convertBucketToColumns', () => { ], [ 'range column if bucket agg is valid range agg', - [{ dataView: stubLogstashDataView, agg: bucketAggs[5], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[5], aggs, metricColumns, visType }], () => { mockConvertToRangeColumn.mockReturnValue({ operationType: 'range', diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.ts b/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.ts index 0f929189f3369..db02b1e09fdce 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.ts @@ -9,9 +9,8 @@ import { BUCKET_TYPES, IAggConfig, METRIC_TYPES } from '@kbn/data-plugin/common'; import type { DataView } from '@kbn/data-views-plugin/common'; import { convertToSchemaConfig } from '../../../vis_schemas'; -import { SchemaConfig } from '../../..'; +import { AggBasedColumn, SchemaConfig } from '../../..'; import { - AggBasedColumn, CommonBucketConverterArgs, convertToDateHistogramColumn, convertToFiltersColumn, @@ -26,6 +25,7 @@ export type BucketAggs = | BUCKET_TYPES.FILTERS | BUCKET_TYPES.RANGE | BUCKET_TYPES.HISTOGRAM; + const SUPPORTED_BUCKETS: string[] = [ BUCKET_TYPES.TERMS, BUCKET_TYPES.DATE_HISTOGRAM, @@ -39,7 +39,7 @@ const isSupportedBucketAgg = (agg: SchemaConfig): agg is SchemaConfig, + { agg, dataView, metricColumns, aggs, visType }: CommonBucketConverterArgs, { label, isSplit = false, @@ -76,7 +76,7 @@ export const getBucketColumns = ( if (field.type !== 'date') { return convertToTermsColumn( agg.aggId ?? '', - { agg, dataView, metricColumns, aggs }, + { agg, dataView, metricColumns, aggs, visType }, label, isSplit ); @@ -102,7 +102,9 @@ export const convertBucketToColumns = ( dataView, metricColumns, aggs, + visType, }: { + visType: string; agg: SchemaConfig | IAggConfig; dataView: DataView; metricColumns: AggBasedColumn[]; @@ -116,7 +118,7 @@ export const convertBucketToColumns = ( return null; } return getBucketColumns( - { agg: currentAgg, dataView, metricColumns, aggs }, + { agg: currentAgg, dataView, metricColumns, aggs, visType }, { label: getLabel(currentAgg), isSplit, diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/configurations/index.ts b/src/plugins/visualizations/common/convert_to_lens/lib/configurations/index.ts index c4592f50836c5..b4934d0bb0c85 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/configurations/index.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/configurations/index.ts @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -export { getPalette } from './palette'; +export { getPalette, getPaletteFromStopsWithColors } from './palette'; export { getPercentageModeConfig } from './percentage_mode'; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/configurations/palette.ts b/src/plugins/visualizations/common/convert_to_lens/lib/configurations/palette.ts index a89177c914996..3f81291fab201 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/configurations/palette.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/configurations/palette.ts @@ -74,6 +74,21 @@ const convertToPercentColorStops = ( return { ...colorStops, stop }; }; +export const getPaletteFromStopsWithColors = ( + config: PaletteConfig, + percentageModeConfig: PercentageModeConfig, + isPercentPaletteSupported: boolean = false +) => { + const percentStopsWithColors = percentageModeConfig.isPercentageMode + ? convertToPercentColorStops(config, percentageModeConfig, isPercentPaletteSupported) + : config; + + return buildCustomPalette( + buildPaletteParams(percentStopsWithColors), + isPercentPaletteSupported && percentageModeConfig.isPercentageMode + ); +}; + export const getPalette = ( params: PaletteParams, percentageModeConfig: PercentageModeConfig, @@ -86,12 +101,10 @@ export const getPalette = ( } const stopsWithColors = getStopsWithColorsFromRanges(colorsRange, colorSchema, invertColors); - const percentStopsWithColors = percentageModeConfig.isPercentageMode - ? convertToPercentColorStops(stopsWithColors, percentageModeConfig, isPercentPaletteSupported) - : stopsWithColors; - return buildCustomPalette( - buildPaletteParams(percentStopsWithColors), - isPercentPaletteSupported && percentageModeConfig.isPercentageMode + return getPaletteFromStopsWithColors( + stopsWithColors, + percentageModeConfig, + isPercentPaletteSupported ); }; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.ts index 0ad2a4072e19d..e79be2ba51516 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.ts @@ -21,6 +21,7 @@ export const createFormulaColumn = (formula: string, agg: SchemaConfig): Formula operationType: 'formula', ...createColumn(agg), references: [], + dataType: 'number', params: { ...params, ...getFormat() }, timeShift: agg.aggParams?.timeShift, meta: { aggId: createAggregationId(agg) }, diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.test.ts index 55ba1e8b5e09d..c46055ca6a9ab 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.test.ts @@ -22,6 +22,7 @@ jest.mock('../utils', () => ({ })); describe('convertToLastValueColumn', () => { + const visType = 'heatmap'; const dataView = stubLogstashDataView; const sortField = dataView.fields[0]; @@ -59,7 +60,13 @@ describe('convertToLastValueColumn', () => { test.each<[string, Parameters, Partial | null]>([ [ 'null if top hits size is more than 1', - [{ agg: { ...topHitAgg, aggParams: { ...topHitAgg.aggParams!, size: 2 } }, dataView }], + [ + { + agg: { ...topHitAgg, aggParams: { ...topHitAgg.aggParams!, size: 2 } }, + dataView, + visType, + }, + ], null, ], [ @@ -74,6 +81,7 @@ describe('convertToLastValueColumn', () => { }, }, dataView, + visType, }, ], null, @@ -88,7 +96,7 @@ describe('convertToLastValueColumn', () => { test('should skip if top hit field is not specified', () => { mockGetFieldNameFromField.mockReturnValue(null); - expect(convertToLastValueColumn({ agg: topHitAgg, dataView })).toBeNull(); + expect(convertToLastValueColumn({ agg: topHitAgg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(0); }); @@ -97,14 +105,14 @@ describe('convertToLastValueColumn', () => { mockGetFieldByName.mockReturnValue(null); dataView.getFieldByName = mockGetFieldByName; - expect(convertToLastValueColumn({ agg: topHitAgg, dataView })).toBeNull(); + expect(convertToLastValueColumn({ agg: topHitAgg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(1); expect(mockGetLabel).toBeCalledTimes(0); }); test('should return top hit column if top hit field is not present in index pattern', () => { - expect(convertToLastValueColumn({ agg: topHitAgg, dataView })).toEqual( + expect(convertToLastValueColumn({ agg: topHitAgg, dataView, visType })).toEqual( expect.objectContaining({ dataType: 'number', label: 'someLabel', diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.ts index 3162cf14e71c3..9525f4b41b7eb 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.ts @@ -25,7 +25,11 @@ const convertToLastValueParams = ( }; export const convertToLastValueColumn = ( - { agg, dataView }: CommonColumnConverterArgs, + { + visType, + agg, + dataView, + }: CommonColumnConverterArgs, reducedTimeRange?: string ): LastValueColumn | null => { const { aggParams } = agg; @@ -43,7 +47,7 @@ export const convertToLastValueColumn = ( } const field = dataView.getFieldByName(fieldName); - if (!isFieldValid(field, SUPPORTED_METRICS[agg.aggType])) { + if (!isFieldValid(visType, field, SUPPORTED_METRICS[agg.aggType])) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.test.ts index 3be17abc46ac1..a0419d46df6b5 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.test.ts @@ -16,6 +16,7 @@ const mockGetFieldByName = jest.fn(); describe('convertToLastValueColumn', () => { const dataView = stubLogstashDataView; + const visType = 'heatmap'; const agg: SchemaConfig = { accessor: 0, @@ -42,6 +43,7 @@ describe('convertToLastValueColumn', () => { convertMetricAggregationColumnWithoutSpecialParams(SUPPORTED_METRICS[METRIC_TYPES.TOP_HITS], { agg, dataView, + visType, }) ).toBeNull(); }); @@ -54,6 +56,7 @@ describe('convertToLastValueColumn', () => { convertMetricAggregationColumnWithoutSpecialParams(SUPPORTED_METRICS[METRIC_TYPES.AVG], { agg, dataView, + visType, }) ).toBeNull(); expect(dataView.getFieldByName).toBeCalledTimes(1); @@ -67,6 +70,7 @@ describe('convertToLastValueColumn', () => { convertMetricAggregationColumnWithoutSpecialParams(SUPPORTED_METRICS[METRIC_TYPES.COUNT], { agg, dataView, + visType, }) ).toEqual(expect.objectContaining({ operationType: 'count' })); expect(dataView.getFieldByName).toBeCalledTimes(1); @@ -80,6 +84,7 @@ describe('convertToLastValueColumn', () => { convertMetricAggregationColumnWithoutSpecialParams(SUPPORTED_METRICS[METRIC_TYPES.AVG], { agg, dataView, + visType, }) ).toEqual( expect.objectContaining({ diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.ts index eb21b9f0fe91d..dd6c8b02687b0 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.ts @@ -78,7 +78,7 @@ export const isMetricWithField = ( export const convertMetricAggregationColumnWithoutSpecialParams = ( aggregation: SupportedMetric, - { agg, dataView }: CommonColumnConverterArgs, + { visType, agg, dataView }: CommonColumnConverterArgs, reducedTimeRange?: string ): MetricAggregationColumnWithoutSpecialParams | null => { if (!isSupportedAggregationWithoutParams(aggregation.name)) { @@ -94,7 +94,7 @@ export const convertMetricAggregationColumnWithoutSpecialParams = ( } const field = dataView.getFieldByName(sourceField); - if (!isFieldValid(field, aggregation)) { + if (!isFieldValid(visType, field, aggregation)) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.test.ts index c28324533c837..65dd1cf40aaef 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.test.ts @@ -40,6 +40,7 @@ jest.mock('../metrics', () => ({ })); describe('convertToOtherParentPipelineAggColumns', () => { + const visType = 'heatmap'; const field = stubLogstashDataView.fields[0].name; const aggs: Array> = [ { @@ -81,6 +82,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -95,6 +97,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -112,6 +115,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -129,6 +133,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -147,6 +152,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -170,6 +176,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -188,6 +195,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -229,6 +237,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { }); describe('convertToCumulativeSumAggColumn', () => { + const visType = 'heatmap'; const field = stubLogstashDataView.fields[0].name; const aggs: Array> = [ { @@ -280,6 +289,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: { ...aggs[1], aggParams: undefined } as SchemaConfig, + visType, }, ], () => { @@ -294,6 +304,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -308,6 +319,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -325,6 +337,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -342,6 +355,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -360,6 +374,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -383,6 +398,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -401,6 +417,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts index ab41ceb259adb..0e0aef11316b2 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts @@ -38,7 +38,7 @@ export const convertToMovingAverageParams = ( }); export const convertToOtherParentPipelineAggColumns = ( - { agg, dataView, aggs }: ExtendedColumnConverterArgs, + { agg, dataView, aggs, visType }: ExtendedColumnConverterArgs, reducedTimeRange?: string ): FormulaColumn | [ParentPipelineAggColumn, AggBasedColumn] | null => { const { aggType } = agg; @@ -63,7 +63,7 @@ export const convertToOtherParentPipelineAggColumns = ( } if (PIPELINE_AGGS.includes(metric.aggType)) { - const formula = getFormulaForPipelineAgg({ agg, aggs, dataView }); + const formula = getFormulaForPipelineAgg({ agg, aggs, dataView, visType }); if (!formula) { return null; } @@ -71,7 +71,7 @@ export const convertToOtherParentPipelineAggColumns = ( return createFormulaColumn(formula, agg); } - const subMetric = convertMetricToColumns(metric, dataView, aggs); + const subMetric = convertMetricToColumns({ agg: metric, dataView, aggs, visType }); if (subMetric === null) { return null; @@ -90,7 +90,7 @@ export const convertToOtherParentPipelineAggColumns = ( }; export const convertToCumulativeSumAggColumn = ( - { agg, dataView, aggs }: ExtendedColumnConverterArgs, + { agg, dataView, aggs, visType }: ExtendedColumnConverterArgs, reducedTimeRange?: string ): | FormulaColumn @@ -119,7 +119,7 @@ export const convertToCumulativeSumAggColumn = ( // create column for sum or count const subMetric = convertMetricAggregationColumnWithoutSpecialParams( subAgg, - { agg: metric as SchemaConfig, dataView }, + { agg: metric as SchemaConfig, dataView, visType }, reducedTimeRange ); @@ -144,7 +144,7 @@ export const convertToCumulativeSumAggColumn = ( subMetric, ]; } else { - const formula = getFormulaForPipelineAgg({ agg, aggs, dataView }); + const formula = getFormulaForPipelineAgg({ agg, aggs, dataView, visType }); if (!formula) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentage_mode.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentage_mode.test.ts index 3b7e8ad7e797f..0ef5d07236d60 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentage_mode.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentage_mode.test.ts @@ -18,6 +18,7 @@ jest.mock('../metrics/formula', () => ({ })); describe('convertToColumnInPercentageMode', () => { + const visType = 'heatmap'; const formula = 'average(some_field)'; const dataView = stubLogstashDataView; @@ -42,7 +43,7 @@ describe('convertToColumnInPercentageMode', () => { test('should return null if it is not possible to build the valid formula', () => { mockGetFormulaForAgg.mockReturnValue(null); - expect(convertToColumnInPercentageMode({ agg, dataView, aggs: [agg] }, {})).toBeNull(); + expect(convertToColumnInPercentageMode({ agg, dataView, aggs: [agg], visType }, {})).toBeNull(); }); test('should return percentage mode over range formula if min and max was passed', () => { @@ -51,7 +52,7 @@ describe('convertToColumnInPercentageMode', () => { params: { format: { id: 'percent' }, formula: `((${formula}) - 0) / (100 - 0)` }, }; expect( - convertToColumnInPercentageMode({ agg, dataView, aggs: [agg] }, { min: 0, max: 100 }) + convertToColumnInPercentageMode({ agg, dataView, aggs: [agg], visType }, { min: 0, max: 100 }) ).toEqual(expect.objectContaining(formulaColumn)); }); @@ -60,7 +61,7 @@ describe('convertToColumnInPercentageMode', () => { operationType: 'formula', params: { format: { id: 'percent' }, formula: `(${formula}) / 10000` }, }; - expect(convertToColumnInPercentageMode({ agg, dataView, aggs: [agg] }, {})).toEqual( + expect(convertToColumnInPercentageMode({ agg, dataView, aggs: [agg], visType }, {})).toEqual( expect.objectContaining(formulaColumn) ); }); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.test.ts index b4cf7f141e928..adfab7f55d1c4 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.test.ts @@ -24,6 +24,7 @@ jest.mock('../utils', () => ({ })); describe('convertToPercentileColumn', () => { + const visType = 'heatmap'; const dataView = stubLogstashDataView; const field = dataView.fields[0].displayName; const aggId = 'pr.10'; @@ -67,23 +68,27 @@ describe('convertToPercentileColumn', () => { test.each< [string, Parameters, Partial | null] >([ - ['null if no percents', [{ agg: { ...agg, aggId: 'pr' }, dataView }], null], + ['null if no percents', [{ agg: { ...agg, aggId: 'pr' }, dataView, visType }], null], [ 'null if no value', - [{ agg: { ...singlePercentileRankAgg, aggParams: undefined }, dataView }], + [{ agg: { ...singlePercentileRankAgg, aggParams: undefined }, dataView, visType }], + null, + ], + ['null if no aggId', [{ agg: { ...agg, aggId: undefined }, dataView, visType }], null], + ['null if no aggParams', [{ agg: { ...agg, aggParams: undefined }, dataView, visType }], null], + [ + 'null if aggId is invalid', + [{ agg: { ...agg, aggId: 'pr.invalid' }, dataView, visType }], null, ], - ['null if no aggId', [{ agg: { ...agg, aggId: undefined }, dataView }], null], - ['null if no aggParams', [{ agg: { ...agg, aggParams: undefined }, dataView }], null], - ['null if aggId is invalid', [{ agg: { ...agg, aggId: 'pr.invalid' }, dataView }], null], [ 'null if values are undefined', - [{ agg: { ...agg, aggParams: { percents: undefined, field } }, dataView }], + [{ agg: { ...agg, aggParams: { percents: undefined, field } }, dataView, visType }], null, ], [ 'null if values are empty', - [{ agg: { ...agg, aggParams: { percents: [], field } }, dataView }], + [{ agg: { ...agg, aggParams: { percents: [], field } }, dataView, visType }], null, ], ])('should return %s', (_, input, expected) => { @@ -96,7 +101,7 @@ describe('convertToPercentileColumn', () => { test('should return null if field is not specified', () => { mockGetFieldNameFromField.mockReturnValue(null); - expect(convertToPercentileColumn({ agg, dataView })).toBeNull(); + expect(convertToPercentileColumn({ agg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(0); }); @@ -105,13 +110,13 @@ describe('convertToPercentileColumn', () => { mockGetFieldByName.mockReturnValueOnce(null); dataView.getFieldByName = mockGetFieldByName; - expect(convertToPercentileColumn({ agg, dataView })).toBeNull(); + expect(convertToPercentileColumn({ agg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(1); }); test('should return percentile rank column for percentiles', () => { - expect(convertToPercentileColumn({ agg, dataView })).toEqual( + expect(convertToPercentileColumn({ agg, dataView, visType })).toEqual( expect.objectContaining({ dataType: 'number', label: 'someOtherLabel', @@ -126,7 +131,7 @@ describe('convertToPercentileColumn', () => { }); test('should return percentile rank column for single percentile', () => { - expect(convertToPercentileColumn({ agg: singlePercentileRankAgg, dataView })).toEqual( + expect(convertToPercentileColumn({ agg: singlePercentileRankAgg, dataView, visType })).toEqual( expect.objectContaining({ dataType: 'number', label: 'someOtherLabel', diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts index de9d4e088b636..9989db1c5dda7 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts @@ -51,6 +51,7 @@ const getPercent = ( export const convertToPercentileColumn = ( { + visType, agg, dataView, }: CommonColumnConverterArgs, @@ -74,7 +75,7 @@ export const convertToPercentileColumn = ( } const field = dataView.getFieldByName(fieldName); - if (!isFieldValid(field, SUPPORTED_METRICS[agg.aggType])) { + if (!isFieldValid(visType, field, SUPPORTED_METRICS[agg.aggType])) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.test.ts index 8a696d51d871b..afeaa9899d107 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.test.ts @@ -24,6 +24,7 @@ jest.mock('../utils', () => ({ })); describe('convertToPercentileRankColumn', () => { + const visType = 'heatmap'; const dataView = stubLogstashDataView; const field = dataView.fields[0].displayName; const aggId = 'pr.10'; @@ -71,23 +72,27 @@ describe('convertToPercentileRankColumn', () => { Partial | null ] >([ - ['null if no percents', [{ agg: { ...agg, aggId: 'pr' }, dataView }], null], + ['null if no percents', [{ agg: { ...agg, aggId: 'pr' }, dataView, visType }], null], [ 'null if no value', - [{ agg: { ...singlePercentileRankAgg, aggParams: undefined }, dataView }], + [{ agg: { ...singlePercentileRankAgg, aggParams: undefined }, dataView, visType }], + null, + ], + ['null if no aggId', [{ agg: { ...agg, aggId: undefined }, dataView, visType }], null], + ['null if no aggParams', [{ agg: { ...agg, aggParams: undefined }, dataView, visType }], null], + [ + 'null if aggId is invalid', + [{ agg: { ...agg, aggId: 'pr.invalid' }, dataView, visType }], null, ], - ['null if no aggId', [{ agg: { ...agg, aggId: undefined }, dataView }], null], - ['null if no aggParams', [{ agg: { ...agg, aggParams: undefined }, dataView }], null], - ['null if aggId is invalid', [{ agg: { ...agg, aggId: 'pr.invalid' }, dataView }], null], [ 'null if values are undefined', - [{ agg: { ...agg, aggParams: { values: undefined, field } }, dataView }], + [{ agg: { ...agg, aggParams: { values: undefined, field } }, dataView, visType }], null, ], [ 'null if values are empty', - [{ agg: { ...agg, aggParams: { values: [], field } }, dataView }], + [{ agg: { ...agg, aggParams: { values: [], field } }, dataView, visType }], null, ], ])('should return %s', (_, input, expected) => { @@ -100,7 +105,7 @@ describe('convertToPercentileRankColumn', () => { test('should return null if field is not specified', () => { mockGetFieldNameFromField.mockReturnValue(null); - expect(convertToPercentileRankColumn({ agg, dataView })).toBeNull(); + expect(convertToPercentileRankColumn({ agg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(0); }); @@ -109,13 +114,13 @@ describe('convertToPercentileRankColumn', () => { mockGetFieldByName.mockReturnValueOnce(null); dataView.getFieldByName = mockGetFieldByName; - expect(convertToPercentileRankColumn({ agg, dataView })).toBeNull(); + expect(convertToPercentileRankColumn({ agg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(1); }); test('should return percentile rank column for percentile ranks', () => { - expect(convertToPercentileRankColumn({ agg, dataView })).toEqual( + expect(convertToPercentileRankColumn({ agg, dataView, visType })).toEqual( expect.objectContaining({ dataType: 'number', label: 'someOtherLabel', @@ -130,7 +135,9 @@ describe('convertToPercentileRankColumn', () => { }); test('should return percentile rank column for single percentile rank', () => { - expect(convertToPercentileRankColumn({ agg: singlePercentileRankAgg, dataView })).toEqual( + expect( + convertToPercentileRankColumn({ agg: singlePercentileRankAgg, dataView, visType }) + ).toEqual( expect.objectContaining({ dataType: 'number', label: 'someOtherLabel', diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.ts index 5124a26543552..8fb55789dd6a7 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.ts @@ -50,6 +50,7 @@ const getPercent = ( export const convertToPercentileRankColumn = ( { + visType, agg, dataView, }: CommonColumnConverterArgs, @@ -69,7 +70,7 @@ export const convertToPercentileRankColumn = ( } const field = dataView.getFieldByName(fieldName); - if (!isFieldValid(field, SUPPORTED_METRICS[agg.aggType])) { + if (!isFieldValid(visType, field, SUPPORTED_METRICS[agg.aggType])) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.test.ts index 8f535c28c8264..5a754fd1c9466 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.test.ts @@ -60,7 +60,6 @@ describe('convertToRangeColumn', () => { params: { type: RANGE_MODES.Histogram, maxBars: 'auto', - ranges: [], }, }, ], diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.ts index 6a9f96fd5ad1e..98200c321935c 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.ts @@ -27,18 +27,17 @@ export const convertToRangeParams = ( return { type: RANGE_MODES.Histogram, maxBars: aggParams.maxBars ?? 'auto', - ranges: [], + includeEmptyRows: aggParams.min_doc_count, }; } else { return { type: RANGE_MODES.Range, maxBars: 'auto', - ranges: - aggParams.ranges?.map((range) => ({ - label: range.label, - from: range.from ?? null, - to: range.to ?? null, - })) ?? [], + ranges: aggParams.ranges?.map((range) => ({ + label: range.label, + from: range.from ?? null, + to: range.to ?? null, + })), }; } }; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.test.ts index 759620650b8a6..6adde7004b69a 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.test.ts @@ -23,6 +23,7 @@ jest.mock('../../../vis_schemas', () => ({ })); describe('convertToSiblingPipelineColumns', () => { + const visType = 'heatmap'; const dataView = stubLogstashDataView; const aggId = 'agg-id-1'; const agg: SchemaConfig = { @@ -46,7 +47,12 @@ describe('convertToSiblingPipelineColumns', () => { test('should return null if aggParams are not defined', () => { expect( - convertToSiblingPipelineColumns({ agg: { ...agg, aggParams: undefined }, aggs: [], dataView }) + convertToSiblingPipelineColumns({ + agg: { ...agg, aggParams: undefined }, + aggs: [], + dataView, + visType, + }) ).toBeNull(); expect(mockConvertMetricToColumns).toBeCalledTimes(0); }); @@ -57,6 +63,7 @@ describe('convertToSiblingPipelineColumns', () => { agg: { ...agg, aggParams: { customMetric: undefined } }, aggs: [], dataView, + visType, }) ).toBeNull(); expect(mockConvertMetricToColumns).toBeCalledTimes(0); @@ -64,7 +71,7 @@ describe('convertToSiblingPipelineColumns', () => { test('should return null if sibling agg is not supported', () => { mockConvertMetricToColumns.mockReturnValue(null); - expect(convertToSiblingPipelineColumns({ agg, aggs: [], dataView })).toBeNull(); + expect(convertToSiblingPipelineColumns({ agg, aggs: [], dataView, visType })).toBeNull(); expect(mockConvertToSchemaConfig).toBeCalledTimes(1); expect(mockConvertMetricToColumns).toBeCalledTimes(1); }); @@ -72,7 +79,7 @@ describe('convertToSiblingPipelineColumns', () => { test('should return column', () => { const column = { operationType: 'formula' }; mockConvertMetricToColumns.mockReturnValue([column]); - expect(convertToSiblingPipelineColumns({ agg, aggs: [], dataView })).toEqual(column); + expect(convertToSiblingPipelineColumns({ agg, aggs: [], dataView, visType })).toEqual(column); expect(mockConvertToSchemaConfig).toBeCalledTimes(1); expect(mockConvertMetricToColumns).toBeCalledTimes(1); }); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts index a8389cb8601e4..c77500a55d5d1 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts @@ -22,11 +22,12 @@ export const convertToSiblingPipelineColumns = ( return null; } - const customMetricColumn = convertMetricToColumns( - { ...convertToSchemaConfig(aggParams.customMetric), label, aggId }, - columnConverterArgs.dataView, - columnConverterArgs.aggs - ); + const customMetricColumn = convertMetricToColumns({ + agg: { ...convertToSchemaConfig(aggParams.customMetric), label, aggId }, + dataView: columnConverterArgs.dataView, + aggs: columnConverterArgs.aggs, + visType: columnConverterArgs.visType, + }); if (!customMetricColumn) { return null; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.test.ts index cbb1f03a6dc2e..c786d6b8c3a6f 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.test.ts @@ -22,6 +22,7 @@ jest.mock('../utils', () => ({ })); describe('convertToStdDeviationFormulaColumns', () => { + const visType = 'heatmap'; const dataView = stubLogstashDataView; const stdLowerAggId = 'agg-id.std_lower'; const stdUpperAggId = 'agg-id.std_upper'; @@ -51,22 +52,25 @@ describe('convertToStdDeviationFormulaColumns', () => { test.each< [string, Parameters, Partial | null] - >([['null if no aggId is passed', [{ agg: { ...agg, aggId: undefined }, dataView }], null]])( - 'should return %s', - (_, input, expected) => { - if (expected === null) { - expect(convertToStdDeviationFormulaColumns(...input)).toBeNull(); - } else { - expect(convertToStdDeviationFormulaColumns(...input)).toEqual( - expect.objectContaining(expected) - ); - } + >([ + [ + 'null if no aggId is passed', + [{ agg: { ...agg, aggId: undefined }, dataView, visType }], + null, + ], + ])('should return %s', (_, input, expected) => { + if (expected === null) { + expect(convertToStdDeviationFormulaColumns(...input)).toBeNull(); + } else { + expect(convertToStdDeviationFormulaColumns(...input)).toEqual( + expect.objectContaining(expected) + ); } - ); + }); test('should return null if field is not present', () => { mockGetFieldNameFromField.mockReturnValue(null); - expect(convertToStdDeviationFormulaColumns({ agg, dataView })).toBeNull(); + expect(convertToStdDeviationFormulaColumns({ agg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(0); }); @@ -74,14 +78,14 @@ describe('convertToStdDeviationFormulaColumns', () => { test("should return null if field doesn't exist in dataView", () => { mockGetFieldByName.mockReturnValue(null); dataView.getFieldByName = mockGetFieldByName; - expect(convertToStdDeviationFormulaColumns({ agg, dataView })).toBeNull(); + expect(convertToStdDeviationFormulaColumns({ agg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(1); }); test('should return null if agg id is invalid', () => { expect( - convertToStdDeviationFormulaColumns({ agg: { ...agg, aggId: 'some-id' }, dataView }) + convertToStdDeviationFormulaColumns({ agg: { ...agg, aggId: 'some-id' }, dataView, visType }) ).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(1); @@ -89,7 +93,11 @@ describe('convertToStdDeviationFormulaColumns', () => { test('should return formula column for lower std deviation', () => { expect( - convertToStdDeviationFormulaColumns({ agg: { ...agg, aggId: stdLowerAggId }, dataView }) + convertToStdDeviationFormulaColumns({ + agg: { ...agg, aggId: stdLowerAggId }, + dataView, + visType, + }) ).toEqual( expect.objectContaining({ label, @@ -102,7 +110,11 @@ describe('convertToStdDeviationFormulaColumns', () => { test('should return formula column for upper std deviation', () => { expect( - convertToStdDeviationFormulaColumns({ agg: { ...agg, aggId: stdUpperAggId }, dataView }) + convertToStdDeviationFormulaColumns({ + agg: { ...agg, aggId: stdUpperAggId }, + dataView, + visType, + }) ).toEqual( expect.objectContaining({ label, diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.ts index f2c218d429bdf..fe4e854759d8f 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.ts @@ -50,7 +50,7 @@ export const getStdDeviationFormula = ( }; export const convertToStdDeviationFormulaColumns = ( - { agg, dataView }: CommonColumnConverterArgs, + { visType, agg, dataView }: CommonColumnConverterArgs, reducedTimeRange?: string ) => { const { aggId } = agg; @@ -64,7 +64,7 @@ export const convertToStdDeviationFormulaColumns = ( return null; } const field = dataView.getFieldByName(fieldName); - if (!isFieldValid(field, SUPPORTED_METRICS[agg.aggType])) { + if (!isFieldValid(visType, field, SUPPORTED_METRICS[agg.aggType])) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/supported_metrics.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/supported_metrics.ts index 17a8ccf26c369..61f3f3961b6dc 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/supported_metrics.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/supported_metrics.ts @@ -18,10 +18,12 @@ interface AggWithFormula { formula: string; } +type SupportedDataTypes = { [key: string]: readonly string[] } & { default: readonly string[] }; + export type AggOptions = { isFullReference: boolean; isFieldRequired: boolean; - supportedDataTypes: readonly string[]; + supportedDataTypes: SupportedDataTypes; } & (T extends Exclude ? Agg : AggWithFormula); // list of supported TSVB aggregation types in Lens @@ -62,9 +64,9 @@ export type SupportedMetrics = LocalSupportedMetrics & { [Key in UnsupportedSupportedMetrics]?: null; }; -const supportedDataTypesWithDate = ['number', 'date', 'histogram'] as const; -const supportedDataTypes = ['number', 'histogram'] as const; -const extendedSupportedDataTypes = [ +const supportedDataTypesWithDate: readonly string[] = ['number', 'date', 'histogram']; +const supportedDataTypes: readonly string[] = ['number', 'histogram']; +const extendedSupportedDataTypes: readonly string[] = [ 'string', 'boolean', 'number', @@ -74,44 +76,44 @@ const extendedSupportedDataTypes = [ 'date', 'date_range', 'murmur3', -] as const; +]; export const SUPPORTED_METRICS: SupportedMetrics = { avg: { name: 'average', isFullReference: false, isFieldRequired: true, - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, cardinality: { name: 'unique_count', isFullReference: false, isFieldRequired: true, - supportedDataTypes: extendedSupportedDataTypes, + supportedDataTypes: { default: extendedSupportedDataTypes }, }, count: { name: 'count', isFullReference: false, isFieldRequired: false, - supportedDataTypes: [], + supportedDataTypes: { default: ['number'] }, }, moving_avg: { name: 'moving_average', isFullReference: true, isFieldRequired: true, - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, derivative: { name: 'differences', isFullReference: true, isFieldRequired: true, - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, cumulative_sum: { name: 'cumulative_sum', isFullReference: true, isFieldRequired: true, - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, avg_bucket: { name: 'formula', @@ -119,7 +121,7 @@ export const SUPPORTED_METRICS: SupportedMetrics = { isFieldRequired: true, isFormula: true, formula: 'overall_average', - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, max_bucket: { name: 'formula', @@ -127,7 +129,7 @@ export const SUPPORTED_METRICS: SupportedMetrics = { isFieldRequired: true, isFormula: true, formula: 'overall_max', - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, min_bucket: { name: 'formula', @@ -135,7 +137,7 @@ export const SUPPORTED_METRICS: SupportedMetrics = { isFieldRequired: true, isFormula: true, formula: 'overall_min', - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, sum_bucket: { name: 'formula', @@ -143,79 +145,91 @@ export const SUPPORTED_METRICS: SupportedMetrics = { isFieldRequired: true, isFormula: true, formula: 'overall_sum', - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, max: { name: 'max', isFullReference: false, isFieldRequired: true, - supportedDataTypes: supportedDataTypesWithDate, + supportedDataTypes: { + default: ['number'], + heatmap: ['number'], + line: ['number'], + area: ['number'], + histogram: ['number'], + }, }, min: { name: 'min', isFullReference: false, isFieldRequired: true, - supportedDataTypes: supportedDataTypesWithDate, + supportedDataTypes: { + default: supportedDataTypesWithDate, + heatmap: ['number'], + line: ['number'], + area: ['number'], + histogram: ['number'], + }, }, percentiles: { name: 'percentile', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, single_percentile: { name: 'percentile', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, percentile_ranks: { name: 'percentile_rank', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, single_percentile_rank: { name: 'percentile_rank', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, sum: { name: 'sum', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, top_hits: { name: 'last_value', isFullReference: false, isFieldRequired: true, - supportedDataTypes: extendedSupportedDataTypes, + supportedDataTypes: { default: extendedSupportedDataTypes }, }, top_metrics: { name: 'last_value', isFullReference: false, isFieldRequired: true, - supportedDataTypes: extendedSupportedDataTypes, + supportedDataTypes: { default: extendedSupportedDataTypes }, }, value_count: { name: 'count', isFullReference: false, isFieldRequired: true, - supportedDataTypes: extendedSupportedDataTypes, + supportedDataTypes: { default: extendedSupportedDataTypes }, }, std_dev: { name: 'standard_deviation', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, median: { name: 'median', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, } as const; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.test.ts index d214ec74b09b1..516ad6b196095 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.test.ts @@ -23,6 +23,7 @@ jest.mock('../../../vis_schemas', () => ({ })); describe('convertToDateHistogramColumn', () => { + const visType = 'heatmap'; const aggId = `some-id`; const aggParams: AggParamsTerms = { field: stubLogstashDataView.fields[0].name, @@ -79,6 +80,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -95,6 +97,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -107,6 +110,8 @@ describe('convertToDateHistogramColumn', () => { size: 5, include: [], exclude: [], + includeIsRegex: false, + excludeIsRegex: false, parentFormat: { id: 'terms' }, orderBy: { type: 'alphabetical' }, orderDirection: 'asc', @@ -123,6 +128,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -135,6 +141,8 @@ describe('convertToDateHistogramColumn', () => { size: 5, include: [], exclude: [], + includeIsRegex: false, + excludeIsRegex: false, parentFormat: { id: 'terms' }, orderBy: { type: 'column', columnId: metricColumns[0].columnId }, orderAgg: metricColumns[0], @@ -152,6 +160,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -170,6 +179,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -188,6 +198,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -208,6 +219,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -220,6 +232,8 @@ describe('convertToDateHistogramColumn', () => { size: 5, include: [], exclude: [], + includeIsRegex: false, + excludeIsRegex: false, parentFormat: { id: 'terms' }, orderBy: { type: 'custom' }, orderAgg: metricColumns[0], diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.ts index 0a50390ec469e..a54a3857e20f6 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.ts @@ -23,6 +23,7 @@ const getOrderByWithAgg = ({ agg, dataView, aggs, + visType, metricColumns, }: CommonBucketConverterArgs): OrderByWithAgg | null => { if (!agg.aggParams) { @@ -37,11 +38,12 @@ const getOrderByWithAgg = ({ if (!agg.aggParams.orderAgg) { return null; } - const orderMetricColumn = convertMetricToColumns( - convertToSchemaConfig(agg.aggParams.orderAgg), + const orderMetricColumn = convertMetricToColumns({ + agg: convertToSchemaConfig(agg.aggParams.orderAgg), dataView, - aggs - ); + aggs, + visType, + }); if (!orderMetricColumn) { return null; } @@ -68,35 +70,43 @@ const getOrderByWithAgg = ({ }; }; +const filterOutEmptyValues = (values: string | Array): number[] | string[] => { + if (typeof values === 'string') { + return Boolean(values) ? [values] : []; + } + + return values.filter((v): v is string | number => { + if (typeof v === 'string') { + return Boolean(v); + } + return true; + }) as string[] | number[]; +}; + export const convertToTermsParams = ({ agg, dataView, aggs, metricColumns, + visType, }: CommonBucketConverterArgs): TermsParams | null => { if (!agg.aggParams) { return null; } - const orderByWithAgg = getOrderByWithAgg({ agg, dataView, aggs, metricColumns }); + const orderByWithAgg = getOrderByWithAgg({ agg, dataView, aggs, metricColumns, visType }); if (orderByWithAgg === null) { return null; } + const exclude = agg.aggParams.exclude ? filterOutEmptyValues(agg.aggParams.exclude) : []; + const include = agg.aggParams.include ? filterOutEmptyValues(agg.aggParams.include) : []; return { size: agg.aggParams.size ?? 10, - include: agg.aggParams.include - ? Array.isArray(agg.aggParams.include) - ? agg.aggParams.include - : [agg.aggParams.include] - : [], - includeIsRegex: agg.aggParams.includeIsRegex, - exclude: agg.aggParams.exclude - ? Array.isArray(agg.aggParams.exclude) - ? agg.aggParams.exclude - : [agg.aggParams.exclude] - : [], - excludeIsRegex: agg.aggParams.excludeIsRegex, + include, + exclude, + includeIsRegex: Boolean(include.length && agg.aggParams.includeIsRegex), + excludeIsRegex: Boolean(exclude.length && agg.aggParams.excludeIsRegex), otherBucket: agg.aggParams.otherBucket, orderDirection: agg.aggParams.order?.value ?? 'desc', parentFormat: { id: 'terms' }, @@ -107,7 +117,7 @@ export const convertToTermsParams = ({ export const convertToTermsColumn = ( aggId: string, - { agg, dataView, aggs, metricColumns }: CommonBucketConverterArgs, + { agg, dataView, aggs, metricColumns, visType }: CommonBucketConverterArgs, label: string, isSplit: boolean ): TermsColumn | null => { @@ -121,7 +131,7 @@ export const convertToTermsColumn = ( return null; } - const params = convertToTermsParams({ agg, dataView, aggs, metricColumns }); + const params = convertToTermsParams({ agg, dataView, aggs, metricColumns, visType }); if (!params) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts index 8e6f9ec9443bb..97ccba39303fc 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts @@ -64,6 +64,7 @@ export interface CommonColumnConverterArgs< > { agg: SchemaConfig; dataView: DataView; + visType: string; } export interface ExtendedColumnConverterArgs< @@ -75,6 +76,7 @@ export interface ExtendedColumnConverterArgs< export interface CommonBucketConverterArgs< Agg extends SupportedAggregation = SupportedAggregation > { + visType: string; agg: SchemaConfig; dataView: DataView; metricColumns: AggBasedColumn[]; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts index 95e128e22b092..72cd07ba03f7c 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts @@ -29,7 +29,7 @@ jest.mock('../utils', () => ({ })); const dataView = stubLogstashDataView; - +const visType = 'heatmap'; const field = stubLogstashDataView.fields[0].name; const aggs: Array> = [ { @@ -97,7 +97,7 @@ describe('getFormulaForPipelineAgg', () => { test.each<[string, Parameters, () => void, string | null]>([ [ 'null if custom metric is invalid', - [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + [{ agg: aggs[0] as SchemaConfig, aggs, dataView, visType }], () => { mockGetMetricFromParentPipelineAgg.mockReturnValue(null); }, @@ -105,7 +105,7 @@ describe('getFormulaForPipelineAgg', () => { ], [ 'null if custom metric type is not supported', - [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + [{ agg: aggs[0] as SchemaConfig, aggs, dataView, visType }], () => { mockGetMetricFromParentPipelineAgg.mockReturnValue({ aggType: METRIC_TYPES.GEO_BOUNDS, @@ -115,7 +115,7 @@ describe('getFormulaForPipelineAgg', () => { ], [ 'correct formula if agg is parent pipeline agg and custom metric is valid and supported pipeline agg', - [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + [{ agg: aggs[0] as SchemaConfig, aggs, dataView, visType }], () => { mockGetMetricFromParentPipelineAgg .mockReturnValueOnce({ @@ -135,7 +135,7 @@ describe('getFormulaForPipelineAgg', () => { ], [ 'correct formula if agg is parent pipeline agg and custom metric is valid and supported not pipeline agg', - [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + [{ agg: aggs[0] as SchemaConfig, aggs, dataView, visType }], () => { mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ aggType: METRIC_TYPES.AVG, @@ -149,7 +149,7 @@ describe('getFormulaForPipelineAgg', () => { ], [ 'correct formula if agg is parent pipeline agg and custom metric is valid and supported percentile rank agg', - [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + [{ agg: aggs[0] as SchemaConfig, aggs, dataView, visType }], () => { mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ aggType: METRIC_TYPES.PERCENTILE_RANKS, @@ -163,7 +163,7 @@ describe('getFormulaForPipelineAgg', () => { ], [ 'correct formula if agg is sibling pipeline agg and custom metric is valid and supported agg', - [{ agg: aggs[1] as SchemaConfig, aggs, dataView }], + [{ agg: aggs[1] as SchemaConfig, aggs, dataView, visType }], () => { mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ aggType: METRIC_TYPES.AVG, @@ -212,6 +212,7 @@ describe('getFormulaForPipelineAgg', () => { agg: aggs[1] as SchemaConfig, aggs, dataView, + visType, }); expect(agg).toBeNull(); }); @@ -244,6 +245,7 @@ describe('getFormulaForPipelineAgg', () => { agg: aggs[1] as SchemaConfig, aggs, dataView, + visType, }); expect(agg).toBeNull(); }); @@ -270,6 +272,7 @@ describe('getFormulaForAgg', () => { agg: { ...aggs[0], aggType: METRIC_TYPES.GEO_BOUNDS, aggParams: { field } }, aggs, dataView, + visType, }, ], () => {}, @@ -277,7 +280,7 @@ describe('getFormulaForAgg', () => { ], [ 'correct pipeline formula if agg is valid pipeline agg', - [{ agg: aggs[0], aggs, dataView }], + [{ agg: aggs[0], aggs, dataView, visType }], () => { mockIsPipeline.mockReturnValue(true); mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ @@ -292,7 +295,7 @@ describe('getFormulaForAgg', () => { ], [ 'correct percentile formula if agg is valid percentile agg', - [{ agg: aggs[2], aggs, dataView }], + [{ agg: aggs[2], aggs, dataView, visType }], () => { mockIsPercentileAgg.mockReturnValue(true); }, @@ -300,7 +303,7 @@ describe('getFormulaForAgg', () => { ], [ 'correct percentile rank formula if agg is valid percentile rank agg', - [{ agg: aggs[3], aggs, dataView }], + [{ agg: aggs[3], aggs, dataView, visType }], () => { mockIsPercentileRankAgg.mockReturnValue(true); }, @@ -308,7 +311,7 @@ describe('getFormulaForAgg', () => { ], [ 'correct standart deviation formula if agg is valid standart deviation agg', - [{ agg: aggs[4], aggs, dataView }], + [{ agg: aggs[4], aggs, dataView, visType }], () => { mockIsStdDevAgg.mockReturnValue(true); }, @@ -316,7 +319,7 @@ describe('getFormulaForAgg', () => { ], [ 'correct metric formula if agg is valid other metric agg', - [{ agg: aggs[5], aggs, dataView }], + [{ agg: aggs[5], aggs, dataView, visType }], () => {}, 'average(bytes)', ], @@ -395,6 +398,7 @@ describe('getFormulaForAgg', () => { >, aggs, dataView, + visType, }); expect(result).toBeNull(); }); @@ -467,6 +471,7 @@ describe('getFormulaForAgg', () => { >, aggs, dataView, + visType, }); expect(result).toBeNull(); } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts index 276ac54e2fc3d..4492cd58ac230 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts @@ -66,7 +66,7 @@ const isDataViewField = (field: string | DataViewField): field is DataViewField return false; }; -const isValidAgg = (agg: SchemaConfig, dataView: DataView) => { +const isValidAgg = (visType: string, agg: SchemaConfig, dataView: DataView) => { const aggregation = SUPPORTED_METRICS[agg.aggType]; if (!aggregation) { return false; @@ -77,7 +77,7 @@ const isValidAgg = (agg: SchemaConfig, dataView: DataView) => { } const sourceField = getFieldNameFromField(agg.aggParams?.field); const field = dataView.getFieldByName(sourceField!); - if (!isFieldValid(field, aggregation)) { + if (!isFieldValid(visType, field, aggregation)) { return false; } } @@ -86,13 +86,14 @@ const isValidAgg = (agg: SchemaConfig, dataView: DataView) => { }; const getFormulaForAggsWithoutParams = ( + visType: string, agg: SchemaConfig, dataView: DataView, selector: string | undefined, reducedTimeRange?: string ) => { const op = SUPPORTED_METRICS[agg.aggType]; - if (!isValidAgg(agg, dataView) || !op) { + if (!isValidAgg(visType, agg, dataView) || !op) { return null; } @@ -101,6 +102,7 @@ const getFormulaForAggsWithoutParams = ( }; const getFormulaForPercentileRanks = ( + visType: string, agg: SchemaConfig, dataView: DataView, selector: string | undefined, @@ -108,7 +110,7 @@ const getFormulaForPercentileRanks = ( ) => { const value = Number(agg.aggId?.split('.')[1]); const op = SUPPORTED_METRICS[agg.aggType]; - if (!isValidAgg(agg, dataView) || !op) { + if (!isValidAgg(visType, agg, dataView) || !op) { return null; } @@ -117,6 +119,7 @@ const getFormulaForPercentileRanks = ( }; const getFormulaForPercentile = ( + visType: string, agg: SchemaConfig, dataView: DataView, selector: string, @@ -124,7 +127,7 @@ const getFormulaForPercentile = ( ) => { const percentile = Number(agg.aggId?.split('.')[1]); const op = SUPPORTED_METRICS[agg.aggType]; - if (!isValidAgg(agg, dataView) || !op) { + if (!isValidAgg(visType, agg, dataView) || !op) { return null; } @@ -138,6 +141,7 @@ const getFormulaForSubMetric = ({ agg, dataView, aggs, + visType, }: ExtendedColumnConverterArgs): string | null => { const op = SUPPORTED_METRICS[agg.aggType]; if (!op) { @@ -148,12 +152,13 @@ const getFormulaForSubMetric = ({ PARENT_PIPELINE_OPS.includes(op.name) || SIBLING_PIPELINE_AGGS.includes(agg.aggType as METRIC_TYPES) ) { - return getFormulaForPipelineAgg({ agg: agg as PipelineAggs, aggs, dataView }); + return getFormulaForPipelineAgg({ agg: agg as PipelineAggs, aggs, dataView, visType }); } if (METRIC_OPS_WITHOUT_PARAMS.includes(op.name)) { const metricAgg = agg as MetricAggsWithoutParams; return getFormulaForAggsWithoutParams( + visType, metricAgg, dataView, metricAgg.aggParams && 'field' in metricAgg.aggParams @@ -168,6 +173,7 @@ const getFormulaForSubMetric = ({ const percentileRanksAgg = agg as SchemaConfig; return getFormulaForPercentileRanks( + visType, percentileRanksAgg, dataView, percentileRanksAgg.aggParams?.field @@ -181,6 +187,7 @@ export const getFormulaForPipelineAgg = ({ agg, dataView, aggs, + visType, }: ExtendedColumnConverterArgs< | METRIC_TYPES.CUMULATIVE_SUM | METRIC_TYPES.DERIVATIVE @@ -205,6 +212,7 @@ export const getFormulaForPipelineAgg = ({ agg: metricAgg, aggs, dataView, + visType, }); if (subFormula === null) { return null; @@ -222,13 +230,15 @@ export const getFormulaForAgg = ({ agg, aggs, dataView, + visType, }: ExtendedColumnConverterArgs) => { if (isPipeline(agg)) { - return getFormulaForPipelineAgg({ agg, aggs, dataView }); + return getFormulaForPipelineAgg({ agg, aggs, dataView, visType }); } if (isPercentileAgg(agg)) { return getFormulaForPercentile( + visType, agg, dataView, getFieldNameFromField(agg.aggParams?.field) ?? '' @@ -237,6 +247,7 @@ export const getFormulaForAgg = ({ if (isPercentileRankAgg(agg)) { return getFormulaForPercentileRanks( + visType, agg, dataView, getFieldNameFromField(agg.aggParams?.field) ?? '' @@ -244,13 +255,14 @@ export const getFormulaForAgg = ({ } if (isStdDevAgg(agg) && agg.aggId) { - if (!isValidAgg(agg, dataView)) { + if (!isValidAgg(visType, agg, dataView)) { return null; } return getStdDeviationFormula(agg.aggId, getFieldNameFromField(agg.aggParams?.field) ?? ''); } return getFormulaForAggsWithoutParams( + visType, agg, dataView, isMetricWithField(agg) ? getFieldNameFromField(agg.aggParams?.field) ?? '' : '' diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts index 1cf3ff0b84064..c7674bf6603c0 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts @@ -9,6 +9,7 @@ import { METRIC_TYPES } from '@kbn/data-plugin/common'; import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { SchemaConfig } from '../../..'; +import { ExtendedColumnConverterArgs } from '../convert'; import { convertMetricToColumns } from './metrics'; const mockConvertMetricAggregationColumnWithoutSpecialParams = jest.fn(); @@ -37,6 +38,8 @@ jest.mock('../convert', () => ({ convertToColumnInPercentageMode: jest.fn(() => mockConvertToColumnInPercentageMode()), })); +const visType = 'heatmap'; + describe('convertMetricToColumns invalid cases', () => { const dataView = stubLogstashDataView; @@ -55,13 +58,18 @@ describe('convertMetricToColumns invalid cases', () => { mockConvertToCumulativeSumAggColumn.mockReturnValue(null); }); + const aggs: ExtendedColumnConverterArgs['aggs'] = []; + test.each<[string, Parameters, null, jest.Mock | undefined]>([ [ 'null if agg is not supported', [ - { aggType: METRIC_TYPES.GEO_BOUNDS } as unknown as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.GEO_BOUNDS } as unknown as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -70,9 +78,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg AVG is not valid', [ - { aggType: METRIC_TYPES.AVG } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.AVG } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -81,9 +92,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg MIN is not valid', [ - { aggType: METRIC_TYPES.MIN } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MIN } as SchemaConfig, + dataView, + aggs: [], + visType, + }, { isPercentageMode: false }, ], null, @@ -92,9 +106,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg MAX is not valid', [ - { aggType: METRIC_TYPES.MAX } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MAX } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -103,9 +120,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg SUM is not valid', [ - { aggType: METRIC_TYPES.SUM } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SUM } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -114,9 +134,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg COUNT is not valid', [ - { aggType: METRIC_TYPES.COUNT } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.COUNT } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -125,9 +148,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg CARDINALITY is not valid', [ - { aggType: METRIC_TYPES.CARDINALITY } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.CARDINALITY } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -136,9 +162,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg VALUE_COUNT is not valid', [ - { aggType: METRIC_TYPES.VALUE_COUNT } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.VALUE_COUNT } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -147,9 +176,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg MEDIAN is not valid', [ - { aggType: METRIC_TYPES.MEDIAN } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MEDIAN } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -158,9 +190,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg STD_DEV is not valid', [ - { aggType: METRIC_TYPES.STD_DEV } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.STD_DEV } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -169,9 +204,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg PERCENTILES is not valid', [ - { aggType: METRIC_TYPES.PERCENTILES } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.PERCENTILES } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -180,9 +218,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg SINGLE_PERCENTILE is not valid', [ - { aggType: METRIC_TYPES.SINGLE_PERCENTILE } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SINGLE_PERCENTILE } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -191,9 +232,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg PERCENTILE_RANKS is not valid', [ - { aggType: METRIC_TYPES.PERCENTILE_RANKS } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.PERCENTILE_RANKS } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -202,9 +246,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg SINGLE_PERCENTILE_RANK is not valid', [ - { aggType: METRIC_TYPES.SINGLE_PERCENTILE_RANK } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SINGLE_PERCENTILE_RANK } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -213,9 +260,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg TOP_HITS is not valid', [ - { aggType: METRIC_TYPES.TOP_HITS } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.TOP_HITS } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -224,9 +274,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg TOP_METRICS is not valid', [ - { aggType: METRIC_TYPES.TOP_METRICS } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.TOP_METRICS } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -235,9 +288,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg CUMULATIVE_SUM is not valid', [ - { aggType: METRIC_TYPES.CUMULATIVE_SUM } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.CUMULATIVE_SUM } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -246,9 +302,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg DERIVATIVE is not valid', [ - { aggType: METRIC_TYPES.DERIVATIVE } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.DERIVATIVE } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -257,9 +316,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg MOVING_FN is not valid', [ - { aggType: METRIC_TYPES.MOVING_FN } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MOVING_FN } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -268,9 +330,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg SUM_BUCKET is not valid', [ - { aggType: METRIC_TYPES.SUM_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SUM_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -279,9 +344,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg MIN_BUCKET is not valid', [ - { aggType: METRIC_TYPES.MIN_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MIN_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -290,9 +358,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg MAX_BUCKET is not valid', [ - { aggType: METRIC_TYPES.MAX_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MAX_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -301,9 +372,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg AVG_BUCKET is not valid', [ - { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, + dataView, + aggs: [], + visType, + }, { isPercentageMode: false }, ], null, @@ -312,9 +386,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg SERIAL_DIFF is not valid', [ - { aggType: METRIC_TYPES.SERIAL_DIFF } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SERIAL_DIFF } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -330,6 +407,7 @@ describe('convertMetricToColumns invalid cases', () => { }); describe('convertMetricToColumns valid cases', () => { const dataView = stubLogstashDataView; + const aggs: ExtendedColumnConverterArgs['aggs'] = []; beforeEach(() => { jest.clearAllMocks(); @@ -353,9 +431,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg AVG is valid', [ - { aggType: METRIC_TYPES.AVG } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.AVG } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -364,9 +445,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg MIN is valid', [ - { aggType: METRIC_TYPES.MIN } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MIN } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -375,9 +459,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg MAX is valid', [ - { aggType: METRIC_TYPES.MAX } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MAX } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -386,9 +473,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg SUM is valid', [ - { aggType: METRIC_TYPES.SUM } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SUM } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -397,9 +487,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg COUNT is valid', [ - { aggType: METRIC_TYPES.COUNT } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.COUNT } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -408,9 +501,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg CARDINALITY is valid', [ - { aggType: METRIC_TYPES.CARDINALITY } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.CARDINALITY } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -419,9 +515,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg VALUE_COUNT is valid', [ - { aggType: METRIC_TYPES.VALUE_COUNT } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.VALUE_COUNT } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -430,9 +529,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg MEDIAN is valid', [ - { aggType: METRIC_TYPES.MEDIAN } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MEDIAN } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -441,9 +543,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg STD_DEV is valid', [ - { aggType: METRIC_TYPES.STD_DEV } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.STD_DEV } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -452,9 +557,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg PERCENTILES is valid', [ - { aggType: METRIC_TYPES.PERCENTILES } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.PERCENTILES } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -463,9 +571,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg SINGLE_PERCENTILE is valid', [ - { aggType: METRIC_TYPES.SINGLE_PERCENTILE } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SINGLE_PERCENTILE } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -474,9 +585,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg PERCENTILE_RANKS is valid', [ - { aggType: METRIC_TYPES.PERCENTILE_RANKS } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.PERCENTILE_RANKS } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -485,9 +599,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg SINGLE_PERCENTILE_RANK is valid', [ - { aggType: METRIC_TYPES.SINGLE_PERCENTILE_RANK } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SINGLE_PERCENTILE_RANK } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -496,9 +613,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg TOP_HITS is valid', [ - { aggType: METRIC_TYPES.TOP_HITS } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.TOP_HITS } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -507,9 +627,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg TOP_METRICS is valid', [ - { aggType: METRIC_TYPES.TOP_METRICS } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.TOP_METRICS } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -518,9 +641,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg CUMULATIVE_SUM is valid', [ - { aggType: METRIC_TYPES.CUMULATIVE_SUM } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.CUMULATIVE_SUM } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -529,9 +655,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg DERIVATIVE is valid', [ - { aggType: METRIC_TYPES.DERIVATIVE } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.DERIVATIVE } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -540,9 +669,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg MOVING_FN is valid', [ - { aggType: METRIC_TYPES.MOVING_FN } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MOVING_FN } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -551,9 +683,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg SUM_BUCKET is valid', [ - { aggType: METRIC_TYPES.SUM_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SUM_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -562,9 +697,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg MIN_BUCKET is valid', [ - { aggType: METRIC_TYPES.MIN_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MIN_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -573,9 +711,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg MAX_BUCKET is valid', [ - { aggType: METRIC_TYPES.MAX_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MAX_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -584,9 +725,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg AVG_BUCKET is valid', [ - { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -595,9 +739,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'column in percentage mode without range if percentageMode is enabled ', [ - { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: true, min: 0, max: 100 }, ], result, @@ -606,9 +753,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'column in percentage mode with range if percentageMode is enabled ', [ - { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: true, min: 0, max: 100 }, ], result, diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts index be4c92cd4ec7f..5d765a6f286ba 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts @@ -7,8 +7,7 @@ */ import { METRIC_TYPES } from '@kbn/data-plugin/common'; -import type { DataView } from '@kbn/data-views-plugin/common'; -import { PercentageModeConfig, SchemaConfig } from '../../..'; +import { PercentageModeConfig } from '../../..'; import { convertMetricAggregationColumnWithoutSpecialParams, convertToOtherParentPipelineAggColumns, @@ -20,14 +19,13 @@ import { convertToCumulativeSumAggColumn, AggBasedColumn, convertToColumnInPercentageMode, + ExtendedColumnConverterArgs, } from '../convert'; import { SUPPORTED_METRICS } from '../convert/supported_metrics'; import { getValidColumns } from '../utils'; export const convertMetricToColumns = ( - agg: SchemaConfig, - dataView: DataView, - aggs: Array>, + { agg, dataView, aggs, visType }: ExtendedColumnConverterArgs, percentageModeConfig: PercentageModeConfig = { isPercentageMode: false } ): AggBasedColumn[] | null => { const supportedAgg = SUPPORTED_METRICS[agg.aggType]; @@ -38,7 +36,7 @@ export const convertMetricToColumns = ( if (percentageModeConfig.isPercentageMode) { const { isPercentageMode, ...minMax } = percentageModeConfig; - const formulaColumn = convertToColumnInPercentageMode({ agg, dataView, aggs }, minMax); + const formulaColumn = convertToColumnInPercentageMode({ agg, dataView, aggs, visType }, minMax); return getValidColumns(formulaColumn); } @@ -54,6 +52,7 @@ export const convertMetricToColumns = ( const columns = convertMetricAggregationColumnWithoutSpecialParams(supportedAgg, { agg, dataView, + visType, }); return getValidColumns(columns); } @@ -61,6 +60,7 @@ export const convertMetricToColumns = ( const columns = convertToStdDeviationFormulaColumns({ agg, dataView, + visType, }); return getValidColumns(columns); } @@ -68,6 +68,7 @@ export const convertMetricToColumns = ( const columns = convertToPercentileColumn({ agg, dataView, + visType, }); return getValidColumns(columns); } @@ -75,6 +76,7 @@ export const convertMetricToColumns = ( const columns = convertToPercentileColumn({ agg, dataView, + visType, }); return getValidColumns(columns); } @@ -82,6 +84,7 @@ export const convertMetricToColumns = ( const columns = convertToPercentileRankColumn({ agg, dataView, + visType, }); return getValidColumns(columns); } @@ -89,6 +92,7 @@ export const convertMetricToColumns = ( const columns = convertToPercentileRankColumn({ agg, dataView, + visType, }); return getValidColumns(columns); } @@ -97,6 +101,7 @@ export const convertMetricToColumns = ( const columns = convertToLastValueColumn({ agg, dataView, + visType, }); return getValidColumns(columns); } @@ -105,6 +110,7 @@ export const convertMetricToColumns = ( agg, dataView, aggs, + visType, }); return getValidColumns(columns); } @@ -114,6 +120,7 @@ export const convertMetricToColumns = ( agg, dataView, aggs, + visType, }); return getValidColumns(columns); } @@ -125,6 +132,7 @@ export const convertMetricToColumns = ( agg, dataView, aggs, + visType, }); return getValidColumns(columns); } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.test.ts index 9855ce44b6602..fe6204d1fb2a1 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.test.ts @@ -24,6 +24,7 @@ jest.mock('../convert', () => ({ })); describe('getPercentageColumnFormulaColumn', () => { + const visType = 'heatmap'; const dataView = stubLogstashDataView; const field = stubLogstashDataView.fields[0].name; const aggs: Array> = [ @@ -52,7 +53,7 @@ describe('getPercentageColumnFormulaColumn', () => { >([ [ 'null if cannot build formula for provided agg', - [{ agg: aggs[0], aggs, dataView }], + [{ agg: aggs[0], aggs, dataView, visType }], () => { mockGetFormulaForAgg.mockReturnValue(null); }, @@ -60,7 +61,7 @@ describe('getPercentageColumnFormulaColumn', () => { ], [ 'null if cannot create formula column for provided arguments', - [{ agg: aggs[0], aggs, dataView }], + [{ agg: aggs[0], aggs, dataView, visType }], () => { mockGetFormulaForAgg.mockReturnValue('test-formula'); mockCreateFormulaColumn.mockReturnValue(null); @@ -69,7 +70,7 @@ describe('getPercentageColumnFormulaColumn', () => { ], [ 'formula column if provided arguments are valid', - [{ agg: aggs[0], aggs, dataView }], + [{ agg: aggs[0], aggs, dataView, visType }], () => { mockGetFormulaForAgg.mockReturnValue('test-formula'); mockCreateFormulaColumn.mockImplementation((formula) => ({ diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.ts index 773851a770db4..8d7194d5c25df 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.ts @@ -14,8 +14,9 @@ export const getPercentageColumnFormulaColumn = ({ agg, aggs, dataView, + visType, }: ExtendedColumnConverterArgs): FormulaColumn | null => { - const metricFormula = getFormulaForAgg({ agg, aggs, dataView }); + const metricFormula = getFormulaForAgg({ agg, aggs, dataView, visType }); if (!metricFormula) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts index f62f61f0c50ab..8a6e70669dcf4 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts @@ -28,6 +28,9 @@ import { GaugeCentralMajorModes, CollapseFunctions, } from '../constants'; +import { ExpressionValueVisDimension } from '../../expression_functions'; + +export type ChartShapes = 'heatmap'; export type CollapseFunction = typeof CollapseFunctions[number]; @@ -277,9 +280,63 @@ export type GaugeVisConfiguration = GaugeState & { layerType: typeof LayerTypes.DATA; }; +export interface HeatmapLegendConfig { + isVisible: boolean; + position: Position; + maxLines?: number; + shouldTruncate?: boolean; + legendSize?: LegendSize; + type: 'heatmap_legend'; +} + +export interface HeatmapGridConfig { + strokeWidth?: number; + strokeColor?: string; + isCellLabelVisible: boolean; + isYAxisLabelVisible: boolean; + isYAxisTitleVisible: boolean; + yTitle?: string; + isXAxisLabelVisible: boolean; + isXAxisTitleVisible: boolean; + xTitle?: string; + type: 'heatmap_grid'; +} +export interface HeatmapArguments { + percentageMode?: boolean; + lastRangeIsRightOpen?: boolean; + showTooltip?: boolean; + highlightInHover?: boolean; + palette?: PaletteOutput; + xAccessor?: string | ExpressionValueVisDimension; + yAccessor?: string | ExpressionValueVisDimension; + valueAccessor?: string | ExpressionValueVisDimension; + splitRowAccessor?: string | ExpressionValueVisDimension; + splitColumnAccessor?: string | ExpressionValueVisDimension; + legend: HeatmapLegendConfig; + gridConfig: HeatmapGridConfig; + ariaLabel?: string; +} + +export type HeatmapLayerState = HeatmapArguments & { + layerId: string; + layerType: LayerType; + valueAccessor?: string; + xAccessor?: string; + yAccessor?: string; + shape: ChartShapes; +}; + +export type Palette = PaletteOutput & { accessor: string }; + +export type HeatmapConfiguration = HeatmapLayerState & { + // need to store the current accessor to reset the color stops at accessor change + palette?: Palette; +}; + export type Configuration = | XYConfiguration | TableVisConfiguration | PartitionVisConfiguration | MetricVisConfiguration - | GaugeVisConfiguration; + | GaugeVisConfiguration + | HeatmapConfiguration; diff --git a/src/plugins/visualizations/common/convert_to_lens/types/params.ts b/src/plugins/visualizations/common/convert_to_lens/types/params.ts index d66822921fb19..4623506496382 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/params.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/params.ts @@ -55,7 +55,7 @@ interface Range { export interface RangeParams extends FormatParams { type: RangeMode; maxBars: 'auto' | number; - ranges: Range[]; + ranges?: Range[]; includeEmptyRows?: boolean; parentFormat?: { id: string; diff --git a/src/plugins/visualizations/common/convert_to_lens/utils.ts b/src/plugins/visualizations/common/convert_to_lens/utils.ts index 6a875bf63bea4..88c2802c421ec 100644 --- a/src/plugins/visualizations/common/convert_to_lens/utils.ts +++ b/src/plugins/visualizations/common/convert_to_lens/utils.ts @@ -18,7 +18,17 @@ export const isAnnotationsLayer = ( export const getIndexPatternIds = (layers: Layer[]) => layers.map(({ indexPatternId }) => indexPatternId); +const isValidFieldType = ( + visType: string, + { supportedDataTypes }: SupportedMetric, + field: DataViewField +) => { + const availableDataTypes = supportedDataTypes[visType] ?? supportedDataTypes.default; + return availableDataTypes.includes(field.type); +}; + export const isFieldValid = ( + visType: string, field: DataViewField | undefined, aggregation: SupportedMetric ): field is DataViewField => { @@ -26,7 +36,7 @@ export const isFieldValid = ( return false; } - if (field && (!field.aggregatable || !aggregation.supportedDataTypes.includes(field.type))) { + if (field && (!field.aggregatable || !isValidFieldType(visType, aggregation, field))) { return false; } diff --git a/src/plugins/visualizations/public/convert_to_lens/index.ts b/src/plugins/visualizations/public/convert_to_lens/index.ts index 73509d49157ae..46fca64199ae1 100644 --- a/src/plugins/visualizations/public/convert_to_lens/index.ts +++ b/src/plugins/visualizations/public/convert_to_lens/index.ts @@ -8,8 +8,10 @@ export { getColumnsFromVis } from './schemas'; export { + convertToFiltersColumn, getPercentageColumnFormulaColumn, getPalette, + getPaletteFromStopsWithColors, getPercentageModeConfig, createStaticValueColumn, } from '../../common/convert_to_lens/lib'; diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts index 54975d08b8486..aa338db367988 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts @@ -70,7 +70,9 @@ describe('getColumnsFromVis', () => { ); const aggConfig = new AggConfig(aggConfigs, {} as AggConfigOptions); - const vis = {} as Vis; + const vis = { + type: { name: 'heatmap' }, + } as Vis; beforeEach(() => { jest.clearAllMocks(); mockGetVisSchemas.mockReturnValue({}); diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.ts index 3a225e540faae..1b44f7cdffda1 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.ts @@ -33,6 +33,7 @@ const areVisSchemasValid = (visSchemas: Schemas, unsupported: Array>, metricsForLayer: Array>, @@ -52,7 +53,7 @@ const createLayer = ( dropEmptyRowsInDateHistogram?: boolean ) => { const metricColumns = metricsForLayer.flatMap((m) => - convertMetricToColumns(m, dataView, allMetrics, percentageModeConfig) + convertMetricToColumns({ agg: m, dataView, aggs: allMetrics, visType }, percentageModeConfig) ); if (metricColumns.includes(null)) { return null; @@ -60,6 +61,7 @@ const createLayer = ( const metricColumnsWithoutNull = metricColumns as AggBasedColumn[]; const { customBucketColumns, customBucketsMap } = getCustomBucketColumns( + visType, customBucketsWithMetricIds, metricColumnsWithoutNull, dataView, @@ -72,6 +74,7 @@ const createLayer = ( } const bucketColumns = getBucketColumns( + visType, visSchemas, buckets, dataView, @@ -84,6 +87,7 @@ const createLayer = ( } const splitBucketColumns = getBucketColumns( + visType, visSchemas, splits, dataView, @@ -181,6 +185,7 @@ export const getColumnsFromVis = ( c.metricIds.some((m) => metricAggIds.includes(m)) ); const layer = createLayer( + vis.type.name, visSchemas, aggs, metrics, @@ -197,6 +202,7 @@ export const getColumnsFromVis = ( } } else { const layer = createLayer( + vis.type.name, visSchemas, aggs, aggs, diff --git a/src/plugins/visualizations/public/convert_to_lens/utils.test.ts b/src/plugins/visualizations/public/convert_to_lens/utils.test.ts index 50f667430a8cb..8c36b28452271 100644 --- a/src/plugins/visualizations/public/convert_to_lens/utils.test.ts +++ b/src/plugins/visualizations/public/convert_to_lens/utils.test.ts @@ -213,6 +213,7 @@ describe('getBucketCollapseFn', () => { describe('getBucketColumns', () => { const dataView = stubLogstashDataView; + const visType = 'heatmap'; beforeEach(() => { jest.clearAllMocks(); @@ -228,7 +229,7 @@ describe('getBucketColumns', () => { [bucketKey]: [], }; - expect(getBucketColumns(visSchemas, keys, dataView, false, [])).toEqual([]); + expect(getBucketColumns(visType, visSchemas, keys, dataView, false, [])).toEqual([]); expect(mockConvertBucketToColumns).toBeCalledTimes(0); }); @@ -254,7 +255,7 @@ describe('getBucketColumns', () => { }; mockConvertBucketToColumns.mockReturnValueOnce(null); - expect(getBucketColumns(visSchemas, keys, dataView, false, [])).toBeNull(); + expect(getBucketColumns(visType, visSchemas, keys, dataView, false, [])).toBeNull(); expect(mockConvertBucketToColumns).toBeCalledTimes(1); }); @@ -280,7 +281,7 @@ describe('getBucketColumns', () => { }; mockConvertBucketToColumns.mockReturnValueOnce([null]); - expect(getBucketColumns(visSchemas, keys, dataView, false, [])).toBeNull(); + expect(getBucketColumns(visType, visSchemas, keys, dataView, false, [])).toBeNull(); expect(mockConvertBucketToColumns).toBeCalledTimes(1); }); test('should return columns', () => { @@ -319,7 +320,7 @@ describe('getBucketColumns', () => { mockConvertBucketToColumns.mockReturnValue(returnValue); - expect(getBucketColumns(visSchemas, keys, dataView, false, [])).toEqual([ + expect(getBucketColumns(visType, visSchemas, keys, dataView, false, [])).toEqual([ ...returnValue, ...returnValue, ]); @@ -592,6 +593,8 @@ describe('sortColumns', () => { }); describe('getColumnIds', () => { + const visType = 'heatmap'; + const colId1 = '0_agg_id'; const colId2 = '1_agg_id'; const colId3 = '2_agg_id'; @@ -694,6 +697,7 @@ describe('getColumnIds', () => { }); expect( getCustomBucketColumns( + visType, customBucketsWithMetricIds, [ { columnId: 'col-3', meta: { aggId: '3' } }, diff --git a/src/plugins/visualizations/public/convert_to_lens/utils.ts b/src/plugins/visualizations/public/convert_to_lens/utils.ts index ba05d29cdeea9..531746ff86d87 100644 --- a/src/plugins/visualizations/public/convert_to_lens/utils.ts +++ b/src/plugins/visualizations/public/convert_to_lens/utils.ts @@ -63,6 +63,7 @@ export const getBucketCollapseFn = ( }; export const getBucketColumns = ( + visType: string, visSchemas: Schemas, keys: Array, dataView: DataView, @@ -78,6 +79,7 @@ export const getBucketColumns = ( { agg: m, dataView, + visType, metricColumns, aggs: visSchemas.metric as Array>, }, @@ -154,6 +156,7 @@ export const sortColumns = ( export const getColumnIds = (columns: AggBasedColumn[]) => columns.map(({ columnId }) => columnId); export const getCustomBucketColumns = ( + visType: string, customBucketsWithMetricIds: Array<{ customBucket: IAggConfig; metricIds: string[]; @@ -167,7 +170,7 @@ export const getCustomBucketColumns = ( const customBucketsMap: Record = {}; customBucketsWithMetricIds.forEach((customBucketWithMetricIds) => { const customBucketColumn = convertBucketToColumns( - { agg: customBucketWithMetricIds.customBucket, dataView, metricColumns, aggs }, + { agg: customBucketWithMetricIds.customBucket, dataView, metricColumns, aggs, visType }, true, dropEmptyRowsInDateHistogram ); diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/types.ts b/x-pack/plugins/lens/public/visualizations/heatmap/types.ts index 08913ad25a7d3..6be8d3b6e8d95 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/types.ts +++ b/x-pack/plugins/lens/public/visualizations/heatmap/types.ts @@ -10,7 +10,7 @@ import type { HeatmapArguments } from '@kbn/expression-heatmap-plugin/common'; import type { LayerType } from '../../../common'; export type ChartShapes = 'heatmap'; -export type HeatmapLayerState = HeatmapArguments & { +export type HeatmapLayerState = Omit & { layerId: string; layerType: LayerType; valueAccessor?: string; diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx index b8e98d03843a9..fc8ef976548b4 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx @@ -17,7 +17,8 @@ import { ThemeServiceStart } from '@kbn/core/public'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { LayerTypes } from '@kbn/expression-xy-plugin/public'; -import type { OperationMetadata, Visualization } from '../../types'; +import { HeatmapConfiguration } from '@kbn/visualizations-plugin/common'; +import type { OperationMetadata, Suggestion, Visualization } from '../../types'; import type { HeatmapVisualizationState } from './types'; import { getSuggestions } from './suggestions'; import { @@ -33,6 +34,7 @@ import { import { HeatmapToolbar } from './toolbar_component'; import { HeatmapDimensionEditor } from './dimension_editor'; import { getSafePaletteParams } from './utils'; +import { FormBasedPersistedState } from '../..'; const groupLabelForHeatmap = i18n.translate('xpack.lens.heatmapVisualization.heatmapGroupLabel', { defaultMessage: 'Magnitude', @@ -525,4 +527,28 @@ export const getHeatmapVisualization = ({ ] : undefined; }, + + getSuggestionFromConvertToLensContext({ suggestions, context }) { + const allSuggestions = suggestions as Array< + Suggestion + >; + const suggestion: Suggestion = { + ...allSuggestions[0], + datasourceState: { + ...allSuggestions[0].datasourceState, + layers: allSuggestions.reduce( + (acc, s) => ({ + ...acc, + ...s.datasourceState?.layers, + }), + {} + ), + }, + visualizationState: { + ...allSuggestions[0].visualizationState, + ...(context.configuration as HeatmapConfiguration), + }, + }; + return suggestion; + }, }); diff --git a/x-pack/test/functional/apps/lens/open_in_lens/agg_based/heatmap.ts b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/heatmap.ts new file mode 100644 index 0000000000000..8556ae601daf9 --- /dev/null +++ b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/heatmap.ts @@ -0,0 +1,219 @@ +/* + * 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 { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const { visualize, lens, visChart, timePicker, visEditor } = getPageObjects([ + 'visualize', + 'lens', + 'visChart', + 'timePicker', + 'visEditor', + ]); + + describe('Heatmap', function describeIndexTests() { + const isNewChartsLibraryEnabled = true; + + before(async () => { + await visualize.initTests(isNewChartsLibraryEnabled); + }); + + beforeEach(async () => { + await visualize.navigateToNewAggBasedVisualization(); + await visualize.clickHeatmapChart(); + await visualize.clickNewSearch(); + await timePicker.setDefaultAbsoluteRange(); + }); + + it('should show the "Edit Visualization in Lens" menu item if no X-axis was specified', async () => { + await visChart.waitForVisualizationRenderingStabilized(); + + expect(await visualize.hasNavigateToLensButton()).to.eql(true); + }); + + it('should show the "Edit Visualization in Lens" menu item', async () => { + await visEditor.clickBucket('X-axis'); + await visEditor.selectAggregation('Terms'); + await visEditor.selectField('machine.os.raw'); + await visEditor.clickGo(); + + expect(await visualize.hasNavigateToLensButton()).to.eql(true); + }); + + it('should convert to Lens', async () => { + await visEditor.clickBucket('X-axis'); + await visEditor.selectAggregation('Terms'); + await visEditor.selectField('machine.os.raw'); + await visEditor.clickGo(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('heatmapChart'); + const debugState = await lens.getCurrentChartDebugState('heatmapChart'); + + if (!debugState) { + throw new Error('Debug state is not available'); + } + + // assert axes + expect(debugState.axes!.x[0].labels).to.eql(['win 8', 'win xp', 'win 7', 'ios', 'osx']); + expect(debugState.axes!.y[0].labels).to.eql(['']); + expect(debugState.heatmap!.cells.length).to.eql(5); + expect(debugState.legend!.items).to.eql([ + { + color: '#006837', + key: '0 - 25', + name: '0 - 25', + }, + { color: '#86CB66', key: '25 - 50', name: '25 - 50' }, + { + color: '#FEFEBD', + key: '50 - 75', + name: '50 - 75', + }, + { + color: '#F88D52', + key: '75 - 100', + name: '75 - 100', + }, + ]); + }); + + it('should convert to Lens if Y-axis is defined, but X-axis is not', async () => { + await visEditor.clickBucket('Y-axis'); + await visEditor.selectAggregation('Terms'); + await visEditor.selectField('machine.os.raw'); + await visEditor.clickGo(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('heatmapChart'); + const debugState = await lens.getCurrentChartDebugState('heatmapChart'); + + if (!debugState) { + throw new Error('Debug state is not available'); + } + + expect(debugState.axes!.x[0].labels).to.eql(['*']); + expect(debugState.axes!.y[0].labels).to.eql(['win 8', 'win xp', 'win 7', 'ios', 'osx']); + expect(debugState.heatmap!.cells.length).to.eql(5); + }); + + it('should respect heatmap colors number', async () => { + await visEditor.clickBucket('X-axis'); + await visEditor.selectAggregation('Terms'); + await visEditor.selectField('machine.os.raw'); + await visEditor.clickGo(); + + await visEditor.clickOptionsTab(); + await visEditor.changeHeatmapColorNumbers(6); + await visEditor.clickGo(); + await visChart.waitForVisualizationRenderingStabilized(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('heatmapChart'); + const debugState = await lens.getCurrentChartDebugState('heatmapChart'); + + if (!debugState) { + throw new Error('Debug state is not available'); + } + + expect(debugState.legend!.items).to.eql([ + { + color: '#006837', + key: '0 - 16.67', + name: '0 - 16.67', + }, + { + color: '#4CB15D', + key: '16.67 - 33.33', + name: '16.67 - 33.33', + }, + { + color: '#B7E075', + key: '33.33 - 50', + name: '33.33 - 50', + }, + { + color: '#FEFEBD', + key: '50 - 66.67', + name: '50 - 66.67', + }, + { + color: '#FDBF6F', + key: '66.67 - 83.33', + name: '66.67 - 83.33', + }, + { + color: '#EA5839', + key: '83.33 - 100', + name: '83.33 - 100', + }, + ]); + }); + + it('should show respect heatmap custom color ranges', async () => { + await visEditor.clickBucket('X-axis'); + await visEditor.selectAggregation('Terms'); + await visEditor.selectField('machine.os.raw'); + await visEditor.clickGo(); + + await visEditor.clickOptionsTab(); + await visEditor.clickOptionsTab(); + await visEditor.clickEnableCustomRanges(); + await visEditor.clickAddRange(); + await visEditor.clickAddRange(); + await visEditor.clickAddRange(); + await visEditor.clickAddRange(); + await visEditor.clickAddRange(); + + await visEditor.clickGo(); + await visChart.waitForVisualizationRenderingStabilized(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('heatmapChart'); + const debugState = await lens.getCurrentChartDebugState('heatmapChart'); + + if (!debugState) { + throw new Error('Debug state is not available'); + } + + expect(debugState.legend!.items).to.eql([ + { + color: '#006837', + key: '0 - 100', + name: '0 - 100', + }, + { + color: '#65BC62', + key: '100 - 200', + name: '100 - 200', + }, + { + color: '#D8EF8C', + key: '200 - 300', + name: '200 - 300', + }, + { + color: '#FEDF8B', + key: '300 - 400', + name: '300 - 400', + }, + { + color: '#F36D43', + key: '400 - 500', + name: '400 - 500', + }, + { + color: '#A50026', + key: '500 - 600', + name: '500 - 600', + }, + ]); + }); + }); +} diff --git a/x-pack/test/functional/apps/lens/open_in_lens/agg_based/index.ts b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/index.ts index 87c9d025893a1..52ef856d53ef6 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/agg_based/index.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/index.ts @@ -15,5 +15,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./gauge')); loadTestFile(require.resolve('./goal')); loadTestFile(require.resolve('./table')); + loadTestFile(require.resolve('./heatmap')); }); }