From dba988bc6583006310d36055a6bf36c2113773b2 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Fri, 7 Oct 2022 11:51:20 +0300 Subject: [PATCH 01/21] Add conversion config for agg based XY --- .../expression_functions/layered_xy_vis_fn.ts | 1 + .../common/expression_functions/xy_vis_fn.ts | 1 + .../common/types/expression_renderers.ts | 1 + .../xy_chart_renderer.tsx | 13 +- .../convert_to_lens/configurations/index.ts | 16 +- .../convert_to_lens/configurations/index.ts | 7 +- .../pie/public/convert_to_lens/index.ts | 2 +- .../convert_to_lens/configurations/index.ts | 31 +- src/plugins/vis_types/xy/kibana.json | 2 +- .../convert_to_lens/configurations/index.ts | 284 ++++++++++++++++++ .../xy/public/convert_to_lens/index.ts | 167 ++++++++++ .../xy/public/convert_to_lens/types.ts | 17 ++ src/plugins/vis_types/xy/public/plugin.ts | 13 +- src/plugins/vis_types/xy/public/services.ts | 4 + src/plugins/vis_types/xy/public/to_ast.ts | 41 +-- .../vis_types/xy/public/utils/common.ts | 62 ++++ .../vis_types/xy/public/vis_types/area.ts | 12 +- .../xy/public/vis_types/histogram.ts | 12 +- .../xy/public/vis_types/horizontal_bar.ts | 12 +- .../vis_types/xy/public/vis_types/line.ts | 12 +- .../lib/convert/sibling_pipeline.ts | 4 +- .../convert_to_lens/lib/convert/types.ts | 2 +- .../convert_to_lens/lib/metrics/index.ts | 1 + .../lib/metrics/static_value.ts | 23 ++ .../common/convert_to_lens/lib/utils.ts | 26 +- .../public/convert_to_lens/index.ts | 5 +- .../public/convert_to_lens/schemas.ts | 55 ++-- .../public/convert_to_lens/utils.ts | 72 ++++- 28 files changed, 778 insertions(+), 120 deletions(-) create mode 100644 src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts create mode 100644 src/plugins/vis_types/xy/public/convert_to_lens/index.ts create mode 100644 src/plugins/vis_types/xy/public/convert_to_lens/types.ts create mode 100644 src/plugins/vis_types/xy/public/utils/common.ts create mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/metrics/static_value.ts diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts index 1eed6aca1cea5..846dd6896f553 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts @@ -61,6 +61,7 @@ export const layeredXyVisFn: LayeredXyVisFn['fn'] = async (data, args, handlers) (handlers.variables?.embeddableTitle as string) ?? handlers.getExecutionContext?.()?.description, }, + canNavigateToLens: Boolean(handlers.variables.canNavigateToLens), }, }; }; diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts index defda933784de..975c611735f77 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts @@ -136,6 +136,7 @@ export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => { (handlers.variables?.embeddableTitle as string) ?? handlers.getExecutionContext?.()?.description, }, + canNavigateToLens: Boolean(handlers.variables.canNavigateToLens), }, }; }; diff --git a/src/plugins/chart_expressions/expression_xy/common/types/expression_renderers.ts b/src/plugins/chart_expressions/expression_xy/common/types/expression_renderers.ts index 2a4baeb22313e..cad328b444443 100644 --- a/src/plugins/chart_expressions/expression_xy/common/types/expression_renderers.ts +++ b/src/plugins/chart_expressions/expression_xy/common/types/expression_renderers.ts @@ -16,6 +16,7 @@ import { XYProps } from './expression_functions'; export interface XYChartProps { args: XYProps; + canNavigateToLens: boolean; } export interface XYRender { diff --git a/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx b/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx index 893037a9f75f1..c3a91894408cc 100644 --- a/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx @@ -52,6 +52,7 @@ interface XyChartRendererDeps { const extractCounterEvents = ( originatingApp: string, { layers, yAxisConfigs }: XYChartProps['args'], + canNavigateToLens: boolean, services: { getDataLayers: typeof getDataLayers; } @@ -149,6 +150,7 @@ const extractCounterEvents = ( (aggregateLayers.length === 1 && dataLayer.splitAccessors?.length) ? 'aggregate_bucket' : undefined, + canNavigateToLens ? `render_${byTypes.mixedXY ? 'mixed_xy' : type}_convertable` : undefined, ] .filter(Boolean) .map((item) => `render_${originatingApp}_${item}`); @@ -188,9 +190,14 @@ export const getXyChartRenderer = ({ const visualizationType = extractVisualizationType(executionContext); if (deps.usageCollection && containerType && visualizationType) { - const uiEvents = extractCounterEvents(visualizationType, config.args, { - getDataLayers, - }); + const uiEvents = extractCounterEvents( + visualizationType, + config.args, + config.canNavigateToLens, + { + getDataLayers, + } + ); if (uiEvents) { deps.usageCollection.reportUiCounter(containerType, METRIC_TYPE.COUNT, uiEvents); diff --git a/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.ts index 39e001c1b87b8..1feb3daeb9a6a 100644 --- a/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.ts +++ b/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.ts @@ -33,19 +33,27 @@ export const getConfiguration = ( bucketCollapseFn, }: { metrics: string[]; - buckets: string[]; + buckets: { + all: string[]; + customBuckets: Record; + }; columnsWithoutReferenced: Column[]; - bucketCollapseFn?: Record; + bucketCollapseFn?: Record; } ): MetricVisConfiguration => { const [metricAccessor] = metrics; - const [breakdownByAccessor] = buckets; + const [breakdownByAccessor] = buckets.all; + const collapseFn = bucketCollapseFn + ? Object.keys(bucketCollapseFn).find((key) => + bucketCollapseFn[key].includes(breakdownByAccessor) + ) + : undefined; return { layerId, layerType: 'data', palette: getPalette(params), metricAccessor, breakdownByAccessor, - collapseFn: Object.values(bucketCollapseFn ?? {})[0], + collapseFn, }; }; diff --git a/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.ts index 9a3420581c1fd..d1d1daf9fe009 100644 --- a/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.ts +++ b/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.ts @@ -68,12 +68,15 @@ export const getConfiguration = ( buckets, }: { metrics: string[]; - buckets: string[]; + buckets: { + all: string[]; + customBuckets: Record; + }; } ): PartitionVisConfiguration => { return { shape: vis.params.isDonut ? 'donut' : 'pie', - layers: getLayers(layerId, vis, metrics, buckets), + layers: getLayers(layerId, vis, metrics, buckets.all), palette: vis.params.palette, }; }; diff --git a/src/plugins/vis_types/pie/public/convert_to_lens/index.ts b/src/plugins/vis_types/pie/public/convert_to_lens/index.ts index 5b1973507c7df..066918cb439af 100644 --- a/src/plugins/vis_types/pie/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/pie/public/convert_to_lens/index.ts @@ -56,7 +56,7 @@ export const convertToLens: ConvertPieToLensVisualization = async (vis, timefilt // doesn't support more than three split slice levels // doesn't support pie without at least one split slice - if (result.buckets.length > 3 || !result.buckets.length) { + if (result.buckets.all.length > 3 || !result.buckets.all.length) { return null; } diff --git a/src/plugins/vis_types/table/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/table/public/convert_to_lens/configurations/index.ts index d98cb917b40ac..5079b25258a75 100644 --- a/src/plugins/vis_types/table/public/convert_to_lens/configurations/index.ts +++ b/src/plugins/vis_types/table/public/convert_to_lens/configurations/index.ts @@ -12,19 +12,21 @@ import { TableVisParams } from '../../../common'; const getColumns = ( params: TableVisParams, metrics: string[], - buckets: string[], columns: Column[], - bucketCollapseFn?: Record + bucketCollapseFn?: Record ) => { const { showTotal, totalFunc } = params; - return columns.map(({ columnId }) => ({ - columnId, - alignment: 'left' as const, - ...(showTotal && metrics.includes(columnId) ? { summaryRow: totalFunc } : {}), - ...(bucketCollapseFn && bucketCollapseFn[columnId] - ? { collapseFn: bucketCollapseFn[columnId] } - : {}), - })); + return columns.map(({ columnId }) => { + const collapseFn = bucketCollapseFn + ? Object.keys(bucketCollapseFn).find((key) => bucketCollapseFn[key].includes(columnId)) + : undefined; + return { + columnId, + alignment: 'left' as const, + ...(showTotal && metrics.includes(columnId) ? { summaryRow: totalFunc } : {}), + ...(collapseFn ? { collapseFn } : {}), + }; + }); }; const getPagination = ({ perPage }: TableVisParams): PagingState => { @@ -54,15 +56,18 @@ export const getConfiguration = ( bucketCollapseFn, }: { metrics: string[]; - buckets: string[]; + buckets: { + all: string[]; + customBuckets: Record; + }; columnsWithoutReferenced: Column[]; - bucketCollapseFn?: Record; + bucketCollapseFn?: Record; } ): TableVisConfiguration => { return { layerId, layerType: 'data', - columns: getColumns(params, metrics, buckets, columnsWithoutReferenced, bucketCollapseFn), + columns: getColumns(params, metrics, columnsWithoutReferenced, bucketCollapseFn), paging: getPagination(params), ...getRowHeight(params), }; diff --git a/src/plugins/vis_types/xy/kibana.json b/src/plugins/vis_types/xy/kibana.json index fa942c1530142..474a70431fc73 100644 --- a/src/plugins/vis_types/xy/kibana.json +++ b/src/plugins/vis_types/xy/kibana.json @@ -3,7 +3,7 @@ "version": "kibana", "ui": true, "server": true, - "requiredPlugins": ["charts", "visualizations", "data", "expressions"], + "requiredPlugins": ["charts", "visualizations", "data", "expressions", "dataViews"], "requiredBundles": ["kibanaUtils", "visDefaultEditor"], "extraPublicDirs": ["common/index"], "owner": { diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts new file mode 100644 index 0000000000000..072535b323c12 --- /dev/null +++ b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts @@ -0,0 +1,284 @@ +/* + * 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 { Position, ScaleType as ECScaleType } from '@elastic/charts'; +import { + Column, + SeriesTypes, + XYConfiguration, + XYDataLayerConfig, + XYReferenceLineLayerConfig, +} from '@kbn/visualizations-plugin/common/convert_to_lens'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { ChartType } from '../..'; +import { + CategoryAxis, + ChartMode, + InterpolationMode, + Scale, + ScaleType, + SeriesParam, + ValueAxis, + VisParams, +} from '../../types'; +import { getCurveType, getLineStyle, getMode, getYAxisPosition } from '../../utils/common'; + +function getYScaleType(scale?: Scale): XYConfiguration['yLeftScale'] | undefined { + const type = scale?.type; + if (type === ScaleType.SquareRoot) { + return ECScaleType.Sqrt; + } + + return type; +} + +function getXScaleType(xColumn?: Column) { + if (xColumn?.dataType === 'date') return ECScaleType.Time; + + if (xColumn?.dataType !== 'number') { + return ECScaleType.Ordinal; + } + + return ECScaleType.Linear; +} + +function getLabelOrientation(data?: CategoryAxis, isTimeChart = false) { + // lens doesn't support 75 as rotate option, we should use 45 instead + return -(data?.labels.rotate === 75 ? 45 : data?.labels.rotate ?? (isTimeChart ? 0 : 90)); +} + +function getExtents(axis: ValueAxis, series: SeriesParam[]) { + // for area and bar charts we should include 0 to bounds + const isAssignedToAreaOrBar = series.some( + (s) => s.valueAxis === axis.id && (s.type === 'histogram' || s.type === 'area') + ); + return { + mode: getMode(axis.scale), + lowerBound: + axis.scale.min !== null + ? isAssignedToAreaOrBar && axis.scale.min && axis.scale.min > 0 + ? 0 + : axis.scale.min + : undefined, + upperBound: + axis.scale.max !== null + ? isAssignedToAreaOrBar && axis.scale.max && axis.scale.max < 0 + ? 0 + : axis.scale.max + : undefined, + enforce: true, + }; +} + +function getSeriesType( + type?: ChartType, + mode?: ChartMode, + isHorizontal?: boolean, + isPercentage?: boolean +): XYDataLayerConfig['seriesType'] { + let seriesType: XYDataLayerConfig['seriesType'] = + type === 'histogram' ? SeriesTypes.BAR : type ?? SeriesTypes.AREA; + + // only bar chart supports horizontal mode + if (isHorizontal && seriesType === SeriesTypes.BAR) { + seriesType = (seriesType + '_horizontal') as XYDataLayerConfig['seriesType']; + } + + // line percentage should convert to area percentage + if (isPercentage) { + seriesType = ((seriesType !== SeriesTypes.LINE ? seriesType : SeriesTypes.AREA) + + '_percentage') as XYDataLayerConfig['seriesType']; + } + + // percentage chart should be stacked + if (isPercentage || (mode === 'stacked' && seriesType !== SeriesTypes.LINE)) { + seriesType = (seriesType + '_stacked') as XYDataLayerConfig['seriesType']; + } + + return seriesType; +} + +function getDataLayers( + layers: Array<{ + indexPatternId: string; + layerId: string; + columns: Column[]; + columnOrder: never[]; + seriesId: string; + isReferenceLineLayer: boolean; + collapseFn?: string; + }>, + series: SeriesParam[], + vis: Vis, + xColumn?: Column +): XYDataLayerConfig[] { + return layers.map((layer) => { + const yAccessors = layer.columns.reduce((acc, column) => { + if (!column.isBucketed) { + acc.push(column.columnId); + } + return acc; + }, []); + const splitAccessor = layer.columns.find( + (column) => column.isBucketed && column.isSplit + )?.columnId; + const serie = series.find((s) => s.data.id === layer.seriesId); + const isHistogram = + xColumn?.operationType === 'date_histogram' || + (xColumn?.operationType === 'range' && xColumn.params.type === 'histogram'); + const yAxis = (vis.params.valueAxes ?? vis.type.visConfig.defaults.valueAxes).find( + (axis) => axis.id === serie?.valueAxis + ); + const isPercentage = yAxis?.scale.mode === 'percentage'; + const isHorizontal = yAxis?.position !== Position.Left && yAxis?.position !== Position.Right; + const seriesType = getSeriesType(serie?.type, serie?.mode, isHorizontal, isPercentage); + return { + layerId: layer.layerId, + accessors: yAccessors, + layerType: 'data', + seriesType, + xAccessor: xColumn?.columnId, + simpleView: false, + splitAccessor, + palette: vis.params.palette ?? vis.type.visConfig.defaults.palette, + yConfig: yAccessors.map((accessor) => ({ + forAccessor: accessor, + axisMode: getYAxisPosition(yAxis?.position ?? 'left'), + })), + xScaleType: getXScaleType(xColumn), + isHistogram, + collapseFn: layer.collapseFn, + }; + }); +} + +function getReferenceLineLayers( + layers: Array<{ + indexPatternId: string; + layerId: string; + columns: Column[]; + columnOrder: never[]; + seriesId: string; + isReferenceLineLayer: boolean; + collapseFn?: string; + }>, + vis: Vis +): XYReferenceLineLayerConfig[] { + const thresholdLineConfig = vis.params.thresholdLine ?? vis.type.visConfig.defaults.thresholdLine; + // threshold line is always assigned to the first value axis + const yAxis = (vis.params.valueAxes ?? vis.type.visConfig.defaults.valueAxes)[0]; + return layers.map((layer) => { + return { + layerType: 'referenceLine', + layerId: layer.layerId, + accessors: layer.columns.map((c) => c.columnId), + yConfig: layer.columns.map((c) => { + return { + forAccessor: c.columnId, + axisMode: getYAxisPosition(yAxis?.position ?? 'left'), + color: thresholdLineConfig.color, + lineWidth: thresholdLineConfig.width !== null ? thresholdLineConfig.width : undefined, + lineStyle: getLineStyle(thresholdLineConfig.style), + }; + }), + }; + }); +} + +export const getConfiguration = ( + layers: Array<{ + indexPatternId: string; + layerId: string; + columns: Column[]; + columnOrder: never[]; + seriesId: string; + isReferenceLineLayer: boolean; + collapseFn?: string; + }>, + series: SeriesParam[], + vis: Vis +): XYConfiguration => { + const legendDisplayFromUiState = vis.uiState.get('vis.legendOpen') ?? true; + const yRightAxis = (vis.params.valueAxes ?? vis.type.visConfig.defaults.valueAxes).find( + (axis) => getYAxisPosition(axis.position) === Position.Right + ); + const yLeftAxis = (vis.params.valueAxes ?? vis.type.visConfig.defaults.valueAxes).find( + (axis) => getYAxisPosition(axis.position) === Position.Left + ); + // as we have only one x-axis + const xAxis = (vis.params.categoryAxes ?? vis.type.visConfig.defaults.categoryAxes)[0]; + const axisTitlesVisibilitySettings = { + x: xAxis.show, + yLeft: yLeftAxis?.show ?? true, + yRight: yRightAxis?.show ?? true, + }; + const xColumn = layers[0].columns.find((c) => c.isBucketed && !c.isSplit); + const isTimeChart = xColumn?.operationType === 'date_histogram'; + const fittingFunction = vis.params.fittingFunction ?? vis.type.visConfig.defaults.fittingFunction; + return { + layers: [ + ...getDataLayers( + layers.filter((l) => !l.isReferenceLineLayer), + series, + vis, + xColumn + ), + ...getReferenceLineLayers( + layers.filter((l) => l.isReferenceLineLayer), + vis + ), + ], + legend: { + isVisible: + legendDisplayFromUiState && (vis.params.addLegend ?? vis.type.visConfig.defaults.addLegend), + position: vis.params.legendPosition ?? vis.type.visConfig.defaults.legendPosition, + legendSize: vis.params.legendSize ?? vis.type.visConfig.defaults.legendSize, + shouldTruncate: vis.params.truncateLegend ?? vis.type.visConfig.defaults.truncateLegend, + maxLines: vis.params.maxLegendLines ?? vis.type.visConfig.defaults.maxLegendLines, + showSingleSeries: true, + }, + fittingFunction: fittingFunction + ? fittingFunction[0].toUpperCase() + fittingFunction.slice(1) + : undefined, + fillOpacity: vis.params.fillOpacity ?? vis.type.visConfig.defaults.fillOpacity, + gridlinesVisibilitySettings: { + x: vis.params.grid.categoryLines ?? vis.type.visConfig.defaults.grid?.categoryLines, + yLeft: + (vis.params.grid.valueAxis ?? vis.type.visConfig.defaults.grid?.valueAxis) === + yLeftAxis?.id, + yRight: + (vis.params.grid.valueAxis ?? vis.type.visConfig.defaults.grid?.valueAxis) === + yRightAxis?.id, + }, + axisTitlesVisibilitySettings, + tickLabelsVisibilitySettings: { + x: axisTitlesVisibilitySettings.x && (xAxis.labels.show ?? true), + yLeft: axisTitlesVisibilitySettings.yLeft && (yLeftAxis?.labels.show ?? true), + yRight: axisTitlesVisibilitySettings.yRight && (yRightAxis?.labels.show ?? true), + }, + labelsOrientation: { + x: getLabelOrientation(xAxis, isTimeChart), + yLeft: getLabelOrientation(yLeftAxis), + yRight: getLabelOrientation(yRightAxis), + }, + yLeftScale: getYScaleType(yLeftAxis?.scale) ?? ECScaleType.Linear, + yRightScale: getYScaleType(yRightAxis?.scale) ?? ECScaleType.Linear, + yLeftExtent: yLeftAxis?.scale ? getExtents(yLeftAxis, series) : undefined, + yRightExtent: yRightAxis?.scale ? getExtents(yRightAxis, series) : undefined, + yTitle: yLeftAxis?.title.text, + yRightTitle: yRightAxis?.title.text, + xTitle: xAxis.title.text, + valueLabels: + vis.params.labels.show ?? vis.type.visConfig.defaults.labels?.show ? 'show' : 'hide', + curveType: getCurveType( + series[0].interpolate === InterpolationMode.StepAfter + ? InterpolationMode.Linear + : series[0].interpolate + ), + }; +}; diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts new file mode 100644 index 0000000000000..2282b5229fde9 --- /dev/null +++ b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts @@ -0,0 +1,167 @@ +/* + * 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, + getVisSchemas, + getDataViewByIndexPatternId, +} from '@kbn/visualizations-plugin/public'; +import uuid from 'uuid'; +import { getDataViewsStart } from '../services'; +import { getSeriesParams } from '../utils/get_series_params'; +import { getConfiguration } from './configurations'; +import { ConvertXYToLensVisualization } 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: ConvertXYToLensVisualization = 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, createStaticValueColumn } = await convertToLensModule; + const result = getColumnsFromVis( + vis, + timefilter, + dataView, + { + buckets: ['segment'], + splits: ['group'], + unsupported: ['split_row', 'split_column', 'radius'], + }, + { + dropEmptyRowsInDateHistogram: true, + supportMixedSiblingPipelineAggs: true, + isPercentageMode: false, + } + ); + + if (result === null) { + return null; + } + + const visSchemas = getVisSchemas(vis, { + timefilter, + timeRange: timefilter.getAbsoluteTime(), + }); + + // doesn't support sibling pipeline aggs and split series together + if (visSchemas.group?.length && Object.keys(result.buckets.customBuckets).length) { + return null; + } + + // doesn't support several metrics with terms split series which uses one of the metrics as order agg + if ( + visSchemas.metric.length > 1 && + result.columns.some( + (c) => c.isSplit && 'orderBy' in c.params && c.params.orderBy.type === 'column' + ) + ) { + return null; + } + + const firstValueAxesId = vis.params.valueAxes[0].id; + const updatedSeries = getSeriesParams( + vis.data.aggs, + vis.params.seriesParams, + 'metric', + firstValueAxesId + ); + + const finalSeriesParams = updatedSeries ?? vis.params.seriesParams; + const visibleSeries = finalSeriesParams.filter( + (param) => param.show && visSchemas.metric.some((m) => m.aggId === param.data.id) + ); + + const visibleYAxes = vis.params.valueAxes.filter((axis) => + visibleSeries.some((seriesParam) => seriesParam.valueAxis === axis.id) + ); + + const positions = visibleYAxes.map((axis) => axis.position); + const uniqPoisitions = new Set(positions); + + // doesn't support more than one axis left/right/top/bottom + if (visibleYAxes.length > 1 && uniqPoisitions.size !== positions.length) { + return null; + } + + const indexPatternId = dataView.id!; + + const layers = visibleSeries.map((s) => { + const layerId = uuid(); + const metrics = result.columns.filter((c) => c.meta.aggId === s.data.id); + const buckets = result.columns.filter((c) => { + if (c.isBucketed) { + if (c.isSplit) { + // as each sibling pipeline agg can have different custom bucket we should get correct one for layer + if ( + Object.keys(result.buckets.customBuckets).some((key) => + result.buckets.customBuckets[key].includes(c.columnId) + ) + ) { + return result.buckets.customBuckets[metrics[0].columnId] === c.columnId; + } + } + + return true; + } + }); + const collapseFn = Object.keys(result.bucketCollapseFn).find((key) => + result.bucketCollapseFn[key].includes(result.buckets.customBuckets[metrics[0].columnId]) + ); + return { + indexPatternId, + layerId, + columns: [...metrics, ...buckets].map(excludeMetaFromColumn), + columnOrder: [], + seriesId: s.data.id, + collapseFn, + isReferenceLineLayer: false, + }; + }); + + if (vis.params.thresholdLine.show) { + layers.push({ + indexPatternId, + layerId: uuid(), + columns: [createStaticValueColumn(vis.params.thresholdLine.value || 0)], + columnOrder: [], + isReferenceLineLayer: true, + collapseFn: undefined, + seriesId: '', + }); + } + + return { + type: 'lnsXY', + layers: layers.map(({ seriesId, collapseFn, isReferenceLineLayer, ...rest }) => rest), + configuration: getConfiguration(layers, visibleSeries, vis), + indexPatternIds: [indexPatternId], + }; +}; diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/types.ts b/src/plugins/vis_types/xy/public/convert_to_lens/types.ts new file mode 100644 index 0000000000000..5fa52b4221107 --- /dev/null +++ b/src/plugins/vis_types/xy/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, XYConfiguration } from '@kbn/visualizations-plugin/common'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { VisParams } from '../types'; + +export type ConvertXYToLensVisualization = ( + vis: Vis, + timefilter?: TimefilterContract +) => Promise | null>; diff --git a/src/plugins/vis_types/xy/public/plugin.ts b/src/plugins/vis_types/xy/public/plugin.ts index 4561006e43e92..ad75af4dfffdb 100644 --- a/src/plugins/vis_types/xy/public/plugin.ts +++ b/src/plugins/vis_types/xy/public/plugin.ts @@ -6,10 +6,11 @@ * Side Public License, v 1. */ -import type { CoreSetup, Plugin } from '@kbn/core/public'; +import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; import type { VisualizationsSetup } from '@kbn/visualizations-plugin/public'; import type { ChartsPluginSetup } from '@kbn/charts-plugin/public'; -import { setUISettings, setPalettesService } from './services'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { setUISettings, setPalettesService, setDataViewsStart } from './services'; import { visTypesDefinitions } from './vis_types'; @@ -24,6 +25,11 @@ export interface VisTypeXyPluginSetupDependencies { charts: ChartsPluginSetup; } +/** @internal */ +export interface VisTypeXyPluginStartDependencies { + dataViews: DataViewsPublicPluginStart; +} + type VisTypeXyCoreSetup = CoreSetup<{}, VisTypeXyPluginStart>; /** @internal */ @@ -42,7 +48,8 @@ export class VisTypeXyPlugin return {}; } - public start() { + public start(core: CoreStart, { dataViews }: VisTypeXyPluginStartDependencies) { + setDataViewsStart(dataViews); return {}; } } diff --git a/src/plugins/vis_types/xy/public/services.ts b/src/plugins/vis_types/xy/public/services.ts index 2358bcb5ede2e..7513f6188ef0e 100644 --- a/src/plugins/vis_types/xy/public/services.ts +++ b/src/plugins/vis_types/xy/public/services.ts @@ -8,6 +8,7 @@ import type { CoreSetup } from '@kbn/core/public'; import type { ChartsPluginSetup } from '@kbn/charts-plugin/public'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { createGetterSetter } from '@kbn/kibana-utils-plugin/public'; @@ -16,3 +17,6 @@ export const [getUISettings, setUISettings] = export const [getPalettesService, setPalettesService] = createGetterSetter('xy charts.palette'); + +export const [getDataViewsStart, setDataViewsStart] = + createGetterSetter('dataViews'); diff --git a/src/plugins/vis_types/xy/public/to_ast.ts b/src/plugins/vis_types/xy/public/to_ast.ts index 4041075b98c4d..95c0fc827685d 100644 --- a/src/plugins/vis_types/xy/public/to_ast.ts +++ b/src/plugins/vis_types/xy/public/to_ast.ts @@ -30,32 +30,15 @@ import { ValueAxis, Scale, ChartMode, - InterpolationMode, ScaleType, } from './types'; import { ChartType } from '../common'; import { getSeriesParams } from './utils/get_series_params'; import { getSafeId } from './utils/accessors'; - -interface Bounds { - min?: string | number; - max?: string | number; -} +import { Bounds, getCurveType, getMode, getYAxisPosition } from './utils/common'; type YDimension = Omit & { accessor: string }; -const getCurveType = (type?: InterpolationMode) => { - switch (type) { - case 'cardinal': - return 'CURVE_MONOTONE_X'; - case 'step-after': - return 'CURVE_STEP_AFTER'; - case 'linear': - default: - return 'LINEAR'; - } -}; - const prepareLengend = (params: VisParams, legendSize?: LegendSize) => { const legend = buildExpressionFunction('legendConfig', { isVisible: params.addLegend, @@ -162,16 +145,6 @@ const prepareLayers = ( return buildExpression([dataLayer]); }; -const getMode = (scale: Scale, bounds?: Bounds) => { - if (scale.defaultYExtents) { - return 'dataBounds'; - } - - if (scale.setYExtents || bounds) { - return 'custom'; - } -}; - const getLabelArgs = (data: CategoryAxis, isTimeChart?: boolean) => { return { truncate: data.labels.truncate, @@ -215,18 +188,6 @@ function getScaleType( return type; } -function getYAxisPosition(position: Position) { - if (position === Position.Top) { - return Position.Right; - } - - if (position === Position.Bottom) { - return Position.Left; - } - - return position; -} - function getXAxisPosition(position: Position) { if (position === Position.Left) { return Position.Bottom; diff --git a/src/plugins/vis_types/xy/public/utils/common.ts b/src/plugins/vis_types/xy/public/utils/common.ts new file mode 100644 index 0000000000000..71d9ab3ff4209 --- /dev/null +++ b/src/plugins/vis_types/xy/public/utils/common.ts @@ -0,0 +1,62 @@ +/* + * 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 { Position } from '@elastic/charts'; +import { AxisExtentConfig } from '@kbn/visualizations-plugin/common/convert_to_lens'; +import { InterpolationMode, Scale, ThresholdLine } from '../types'; + +export interface Bounds { + min?: string | number; + max?: string | number; +} + +export const getCurveType = (type?: InterpolationMode) => { + switch (type) { + case 'cardinal': + return 'CURVE_MONOTONE_X'; + case 'step-after': + return 'CURVE_STEP_AFTER'; + case 'linear': + default: + return 'LINEAR'; + } +}; + +export const getMode = (scale: Scale, bounds?: Bounds): AxisExtentConfig['mode'] => { + if (scale.defaultYExtents) { + return 'dataBounds'; + } + + if (scale.setYExtents || bounds) { + return 'custom'; + } + + return 'full'; +}; + +export const getYAxisPosition = (position: Position) => { + if (position === Position.Top) { + return Position.Right; + } + + if (position === Position.Bottom) { + return Position.Left; + } + + return position; +}; + +export const getLineStyle = (style: ThresholdLine['style']) => { + switch (style) { + case 'full': + return 'solid'; + case 'dashed': + case 'dot-dashed': + return style; + } +}; diff --git a/src/plugins/vis_types/xy/public/vis_types/area.ts b/src/plugins/vis_types/xy/public/vis_types/area.ts index 0ca07c8067457..bbb930ca0e4a1 100644 --- a/src/plugins/vis_types/xy/public/vis_types/area.ts +++ b/src/plugins/vis_types/xy/public/vis_types/area.ts @@ -12,7 +12,7 @@ import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { Fit, Position } from '@elastic/charts'; import { AggGroupNames } from '@kbn/data-plugin/public'; -import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; +import { VisTypeDefinition, VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { defaultCountLabel, LabelRotation } from '@kbn/charts-plugin/public'; import { @@ -22,13 +22,15 @@ import { AxisMode, ThresholdLineStyle, InterpolationMode, + VisParams, } from '../types'; import { toExpressionAst } from '../to_ast'; import { ChartType } from '../../common'; import { optionTabs } from '../editor/common_config'; import { getVisTypeFromParams } from './get_vis_type_from_params'; +import { convertToLens } from '../convert_to_lens'; -export const areaVisTypeDefinition = { +export const areaVisTypeDefinition: VisTypeDefinition = { name: 'area', title: i18n.translate('visTypeXy.area.areaTitle', { defaultMessage: 'Area' }), icon: 'visArea', @@ -38,6 +40,12 @@ export const areaVisTypeDefinition = { fetchDatatable: true, toExpressionAst, getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], + navigateToLens: async (vis, timefilter) => (vis ? convertToLens(vis, timefilter) : null), + getExpressionVariables: async (vis, timeFilter) => { + return { + canNavigateToLens: Boolean(vis?.params ? await convertToLens(vis, timeFilter) : null), + }; + }, updateVisTypeOnParamsChange: getVisTypeFromParams, visConfig: { defaults: { diff --git a/src/plugins/vis_types/xy/public/vis_types/histogram.ts b/src/plugins/vis_types/xy/public/vis_types/histogram.ts index 680186eb330f9..20f3f18c68294 100644 --- a/src/plugins/vis_types/xy/public/vis_types/histogram.ts +++ b/src/plugins/vis_types/xy/public/vis_types/histogram.ts @@ -12,7 +12,7 @@ import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { Position } from '@elastic/charts'; import { AggGroupNames } from '@kbn/data-plugin/public'; -import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; +import { VisTypeDefinition, VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { defaultCountLabel, LabelRotation } from '@kbn/charts-plugin/public'; import { @@ -22,13 +22,15 @@ import { AxisMode, ThresholdLineStyle, InterpolationMode, + VisParams, } from '../types'; import { toExpressionAst } from '../to_ast'; import { ChartType } from '../../common'; import { optionTabs } from '../editor/common_config'; import { getVisTypeFromParams } from './get_vis_type_from_params'; +import { convertToLens } from '../convert_to_lens'; -export const histogramVisTypeDefinition = { +export const histogramVisTypeDefinition: VisTypeDefinition = { name: 'histogram', title: i18n.translate('visTypeXy.histogram.histogramTitle', { defaultMessage: 'Vertical bar', @@ -41,6 +43,12 @@ export const histogramVisTypeDefinition = { toExpressionAst, getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], updateVisTypeOnParamsChange: getVisTypeFromParams, + navigateToLens: async (vis, timefilter) => (vis ? convertToLens(vis, timefilter) : null), + getExpressionVariables: async (vis, timeFilter) => { + return { + canNavigateToLens: Boolean(vis?.params ? await convertToLens(vis, timeFilter) : null), + }; + }, visConfig: { defaults: { type: ChartType.Histogram, diff --git a/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts b/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts index 25fc3142e0e98..fbc07410ea7fc 100644 --- a/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts +++ b/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts @@ -12,7 +12,7 @@ import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { Position } from '@elastic/charts'; import { AggGroupNames } from '@kbn/data-plugin/public'; -import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; +import { VisTypeDefinition, VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { defaultCountLabel, LabelRotation } from '@kbn/charts-plugin/public'; import { @@ -22,13 +22,15 @@ import { AxisMode, ThresholdLineStyle, InterpolationMode, + VisParams, } from '../types'; import { toExpressionAst } from '../to_ast'; import { ChartType } from '../../common'; import { optionTabs } from '../editor/common_config'; import { getVisTypeFromParams } from './get_vis_type_from_params'; +import { convertToLens } from '../convert_to_lens'; -export const horizontalBarVisTypeDefinition = { +export const horizontalBarVisTypeDefinition: VisTypeDefinition = { name: 'horizontal_bar', title: i18n.translate('visTypeXy.horizontalBar.horizontalBarTitle', { defaultMessage: 'Horizontal bar', @@ -40,6 +42,12 @@ export const horizontalBarVisTypeDefinition = { fetchDatatable: true, toExpressionAst, getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], + navigateToLens: async (vis, timefilter) => (vis ? convertToLens(vis, timefilter) : null), + getExpressionVariables: async (vis, timeFilter) => { + return { + canNavigateToLens: Boolean(vis?.params ? await convertToLens(vis, timeFilter) : null), + }; + }, updateVisTypeOnParamsChange: getVisTypeFromParams, visConfig: { defaults: { diff --git a/src/plugins/vis_types/xy/public/vis_types/line.ts b/src/plugins/vis_types/xy/public/vis_types/line.ts index e0c7e081573f3..ca5e4172c0ac1 100644 --- a/src/plugins/vis_types/xy/public/vis_types/line.ts +++ b/src/plugins/vis_types/xy/public/vis_types/line.ts @@ -12,7 +12,7 @@ import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { Position, Fit } from '@elastic/charts'; import { AggGroupNames } from '@kbn/data-plugin/public'; -import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; +import { VisTypeDefinition, VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { defaultCountLabel, LabelRotation } from '@kbn/charts-plugin/public'; import { @@ -22,13 +22,15 @@ import { AxisMode, ThresholdLineStyle, InterpolationMode, + VisParams, } from '../types'; import { toExpressionAst } from '../to_ast'; import { ChartType } from '../../common'; import { optionTabs } from '../editor/common_config'; import { getVisTypeFromParams } from './get_vis_type_from_params'; +import { convertToLens } from '../convert_to_lens'; -export const lineVisTypeDefinition = { +export const lineVisTypeDefinition: VisTypeDefinition = { name: 'line', title: i18n.translate('visTypeXy.line.lineTitle', { defaultMessage: 'Line' }), icon: 'visLine', @@ -38,6 +40,12 @@ export const lineVisTypeDefinition = { fetchDatatable: true, toExpressionAst, getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], + navigateToLens: async (vis, timefilter) => (vis ? convertToLens(vis, timefilter) : null), + getExpressionVariables: async (vis, timeFilter) => { + return { + canNavigateToLens: Boolean(vis?.params ? await convertToLens(vis, timeFilter) : null), + }; + }, updateVisTypeOnParamsChange: getVisTypeFromParams, visConfig: { defaults: { 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 03e1d955dd045..a8389cb8601e4 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 @@ -13,7 +13,7 @@ import { convertToSchemaConfig } from '../../../vis_schemas'; export const convertToSiblingPipelineColumns = ( columnConverterArgs: ExtendedColumnConverterArgs ): AggBasedColumn | null => { - const { aggParams, label } = columnConverterArgs.agg; + const { aggParams, label, aggId } = columnConverterArgs.agg; if (!aggParams) { return null; } @@ -23,7 +23,7 @@ export const convertToSiblingPipelineColumns = ( } const customMetricColumn = convertMetricToColumns( - { ...convertToSchemaConfig(aggParams.customMetric), label }, + { ...convertToSchemaConfig(aggParams.customMetric), label, aggId }, columnConverterArgs.dataView, columnConverterArgs.aggs ); 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 3dfaee67a61e0..8e6f9ec9443bb 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 @@ -58,7 +58,7 @@ export type SiblingPipelineMetric = | METRIC_TYPES.MIN_BUCKET | METRIC_TYPES.MAX_BUCKET; -export type BucketColumn = DateHistogramColumn | TermsColumn | FiltersColumn; +export type BucketColumn = DateHistogramColumn | TermsColumn | FiltersColumn | RangeColumn; export interface CommonColumnConverterArgs< Agg extends SupportedAggregation = SupportedAggregation > { diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/index.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/index.ts index c34e328332339..e912ef80045be 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/index.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/index.ts @@ -9,3 +9,4 @@ export { getFormulaForPipelineAgg } from './formula'; export { convertMetricToColumns } from './metrics'; export { getPercentageColumnFormulaColumn } from './percentage_formula'; +export { createStaticValueColumn } from './static_value'; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/static_value.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/static_value.ts new file mode 100644 index 0000000000000..8a763aa80c678 --- /dev/null +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/static_value.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import uuid from 'uuid'; +import { StaticValueColumn } from '../../types'; + +export const createStaticValueColumn = (staticValue: number): StaticValueColumn => ({ + columnId: uuid(), + operationType: 'static_value', + references: [], + dataType: 'number', + isStaticValue: true, + isBucketed: false, + isSplit: false, + params: { + value: staticValue.toString(), + }, +}); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/utils.ts b/src/plugins/visualizations/common/convert_to_lens/lib/utils.ts index c4e5c5474bf0c..ce50312d92cf3 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/utils.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/utils.ts @@ -151,19 +151,19 @@ export const isStdDevAgg = (metric: SchemaConfig): metric is SchemaConfig { - return metrics.reduce((acc, metric) => { - if ( - isSiblingPipeline(metric) && - metric.aggParams?.customBucket && - acc.every( - (bucket) => - !isEqual( - omit(metric.aggParams?.customBucket?.serialize(), ['id']), - omit(bucket.serialize(), ['id']) - ) - ) - ) { - acc.push(metric.aggParams.customBucket); + return metrics.reduce>((acc, metric) => { + if (isSiblingPipeline(metric) && metric.aggParams?.customBucket && metric.aggId) { + const customBucket = acc.find((bucket) => + isEqual( + omit(metric.aggParams?.customBucket?.serialize(), ['id']), + omit(bucket.customBucket.serialize(), ['id']) + ) + ); + if (customBucket) { + customBucket.metricIds.push(metric.aggId); + } else { + acc.push({ customBucket: metric.aggParams.customBucket, metricIds: [metric.aggId] }); + } } return acc; diff --git a/src/plugins/visualizations/public/convert_to_lens/index.ts b/src/plugins/visualizations/public/convert_to_lens/index.ts index 9cd2ba2768ad7..84cab2876566f 100644 --- a/src/plugins/visualizations/public/convert_to_lens/index.ts +++ b/src/plugins/visualizations/public/convert_to_lens/index.ts @@ -7,4 +7,7 @@ */ export { getColumnsFromVis } from './schemas'; -export { getPercentageColumnFormulaColumn } from '../../common/convert_to_lens/lib'; +export { + getPercentageColumnFormulaColumn, + createStaticValueColumn, +} from '../../common/convert_to_lens/lib'; diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.ts index ecfbbf34ad9c9..87e06abf803d4 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.ts @@ -10,13 +10,14 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import { METRIC_TYPES, TimefilterContract } from '@kbn/data-plugin/public'; import { AggBasedColumn, PercentageModeConfig, SchemaConfig } from '../../common'; import { convertMetricToColumns } from '../../common/convert_to_lens/lib/metrics'; -import { convertBucketToColumns } from '../../common/convert_to_lens/lib/buckets'; import { getCustomBucketsFromSiblingAggs } from '../../common/convert_to_lens/lib/utils'; +import { BucketColumn } from '../../common/convert_to_lens/lib'; import type { Vis } from '../types'; import { getVisSchemas, Schemas } from '../vis_schemas'; import { getBucketCollapseFn, getBucketColumns, + getCustomBuckets, getColumnIds, getColumnsWithoutReferenced, getMetricsWithoutDuplicates, @@ -46,6 +47,7 @@ export const getColumnsFromVis = ( } = {}, config?: { dropEmptyRowsInDateHistogram?: boolean; + supportMixedSiblingPipelineAggs?: boolean; } & (PercentageModeConfig | void) ) => { const { dropEmptyRowsInDateHistogram, ...percentageModeConfig } = config ?? { @@ -56,14 +58,17 @@ export const getColumnsFromVis = ( timeRange: timefilter.getAbsoluteTime(), }); - if (!isValidVis(visSchemas) || !areVisSchemasValid(visSchemas, unsupported)) { + if ( + !isValidVis(visSchemas, config?.supportMixedSiblingPipelineAggs) || + !areVisSchemasValid(visSchemas, unsupported) + ) { return null; } - const customBuckets = getCustomBucketsFromSiblingAggs(visSchemas.metric); + const customBucketsWithMetricIds = getCustomBucketsFromSiblingAggs(visSchemas.metric); // doesn't support sibbling pipeline aggs with different bucket aggs - if (customBuckets.length > 1) { + if (!config?.supportMixedSiblingPipelineAggs && customBucketsWithMetricIds.length > 1) { return null; } @@ -78,18 +83,17 @@ export const getColumnsFromVis = ( return null; } const metrics = metricColumns as AggBasedColumn[]; - const customBucketColumns = []; - - if (customBuckets.length) { - const customBucketColumn = convertBucketToColumns( - { agg: customBuckets[0], dataView, metricColumns: metrics, aggs }, - false, - dropEmptyRowsInDateHistogram - ); - if (!customBucketColumn) { - return null; - } - customBucketColumns.push(customBucketColumn); + + const { customBucketColumns, customBucketsMap } = getCustomBuckets( + customBucketsWithMetricIds, + metrics, + dataView, + aggs, + config?.dropEmptyRowsInDateHistogram + ); + + if (customBucketColumns.includes(null)) { + return null; } const bucketColumns = getBucketColumns( @@ -117,7 +121,12 @@ export const getColumnsFromVis = ( } const columns = sortColumns( - [...metrics, ...bucketColumns, ...splitBucketColumns, ...customBucketColumns], + [ + ...metrics, + ...bucketColumns, + ...splitBucketColumns, + ...(customBucketColumns as BucketColumn[]), + ], visSchemas, [...buckets, ...splits], metricsWithoutDuplicates @@ -127,8 +136,16 @@ export const getColumnsFromVis = ( return { metrics: getColumnIds(columnsWithoutReferenced.filter((с) => !с.isBucketed)), - buckets: getColumnIds(columnsWithoutReferenced.filter((c) => c.isBucketed)), - bucketCollapseFn: getBucketCollapseFn(visSchemas.metric, customBucketColumns), + buckets: { + all: getColumnIds(columnsWithoutReferenced.filter((c) => c.isBucketed)), + customBuckets: customBucketsMap, + }, + bucketCollapseFn: getBucketCollapseFn( + visSchemas.metric, + customBucketColumns as BucketColumn[], + customBucketsMap, + metrics + ), columnsWithoutReferenced, columns, }; diff --git a/src/plugins/visualizations/public/convert_to_lens/utils.ts b/src/plugins/visualizations/public/convert_to_lens/utils.ts index a5337568b5568..8ec04852834a4 100644 --- a/src/plugins/visualizations/public/convert_to_lens/utils.ts +++ b/src/plugins/visualizations/public/convert_to_lens/utils.ts @@ -7,10 +7,11 @@ */ import type { DataView } from '@kbn/data-views-plugin/common'; -import { METRIC_TYPES } from '@kbn/data-plugin/public'; +import { IAggConfig, METRIC_TYPES } from '@kbn/data-plugin/public'; import { AggBasedColumn, SchemaConfig, SupportedAggregation } from '../../common'; import { convertBucketToColumns } from '../../common/convert_to_lens/lib/buckets'; import { isSiblingPipeline } from '../../common/convert_to_lens/lib/utils'; +import { BucketColumn } from '../../common/convert_to_lens/lib'; import { Schemas } from '../vis_schemas'; export const isReferenced = (columnId: string, references: string[]) => @@ -25,14 +26,31 @@ export const getColumnsWithoutReferenced = (columns: AggBasedColumn[]) => { export const getBucketCollapseFn = ( metrics: Array>, - customBucketColumns: AggBasedColumn[] + customBucketColumns: AggBasedColumn[], + customBucketsMap: Record, + metricColumns: AggBasedColumn[] ) => { - const collapseFn = metrics.find((m) => isSiblingPipeline(m))?.aggType.split('_')[0]; - return customBucketColumns.length - ? { - [customBucketColumns[0].columnId]: collapseFn, + const collapseFnMap: Record = { + min: [], + max: [], + sum: [], + avg: [], + }; + customBucketColumns.forEach((bucket) => { + const metricColumnsIds = Object.keys(customBucketsMap).filter( + (key) => customBucketsMap[key] === bucket.columnId + ); + metricColumnsIds.forEach((metricColumnsId) => { + const metricColumn = metricColumns.find((c) => c.columnId === metricColumnsId)!; + const collapseFn = metrics + .find((m) => m.aggId === metricColumn.meta.aggId) + ?.aggType.split('_')[0]; + if (collapseFn) { + collapseFnMap[collapseFn].push(bucket.columnId); } - : {}; + }); + }); + return collapseFnMap; }; export const getBucketColumns = ( @@ -67,7 +85,7 @@ export const getBucketColumns = ( return columns; }; -export const isValidVis = (visSchemas: Schemas) => { +export const isValidVis = (visSchemas: Schemas, supportMixedSiblingPipelineAggs?: boolean) => { const { metric } = visSchemas; const siblingPipelineAggs = metric.filter((m) => isSiblingPipeline(m)); @@ -76,7 +94,10 @@ export const isValidVis = (visSchemas: Schemas) => { } // doesn't support mixed sibling pipeline aggregations - if (siblingPipelineAggs.some((agg) => agg.aggType !== siblingPipelineAggs[0].aggType)) { + if ( + siblingPipelineAggs.some((agg) => agg.aggType !== siblingPipelineAggs[0].aggType) && + !supportMixedSiblingPipelineAggs + ) { return false; } @@ -121,3 +142,36 @@ export const sortColumns = ( }; export const getColumnIds = (columns: AggBasedColumn[]) => columns.map(({ columnId }) => columnId); + +export const getCustomBuckets = ( + customBucketsWithMetricIds: Array<{ + customBucket: IAggConfig; + metricIds: string[]; + }>, + metricColumns: AggBasedColumn[], + dataView: DataView, + aggs: Array>, + dropEmptyRowsInDateHistogram?: boolean +) => { + const customBucketColumns: Array = []; + const customBucketsMap: Record = {}; + customBucketsWithMetricIds.forEach((customBucketWithMetricIds) => { + const customBucketColumn = convertBucketToColumns( + { agg: customBucketWithMetricIds.customBucket, dataView, metricColumns, aggs }, + true, + dropEmptyRowsInDateHistogram + ); + customBucketColumns.push(customBucketColumn); + if (customBucketColumn) { + customBucketWithMetricIds.metricIds.forEach((metricAggId) => { + const columnId = metricColumns.find( + (metricColumn) => metricColumn?.meta.aggId === metricAggId + )?.columnId; + if (columnId) { + customBucketsMap[columnId] = customBucketColumn.columnId; + } + }); + } + }); + return { customBucketColumns, customBucketsMap }; +}; From b3440c4fdc89b429d81fd4bb9e184b3851a11df1 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Fri, 7 Oct 2022 15:55:43 +0300 Subject: [PATCH 02/21] Fixed line stacked charts and buckets for layers --- .../metric/public/convert_to_lens/index.ts | 2 +- .../convert_to_lens/configurations/index.ts | 34 +++++---------- .../xy/public/convert_to_lens/index.ts | 43 ++++++++++++++----- 3 files changed, 43 insertions(+), 36 deletions(-) diff --git a/src/plugins/vis_types/metric/public/convert_to_lens/index.ts b/src/plugins/vis_types/metric/public/convert_to_lens/index.ts index 7675cbcc1d714..1c87aa6aa3903 100644 --- a/src/plugins/vis_types/metric/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/metric/public/convert_to_lens/index.ts @@ -62,7 +62,7 @@ export const convertToLens: ConvertMetricVisToLensVisualization = async (vis, ti } // for now, multiple metrics are not supported - if (result.metrics.length > 1 || result.buckets.length > 1) { + if (result.metrics.length > 1 || result.buckets.all.length > 1) { return null; } diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts index 072535b323c12..ceb83b33e1abb 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts @@ -15,6 +15,7 @@ import { XYReferenceLineLayerConfig, } from '@kbn/visualizations-plugin/common/convert_to_lens'; import { Vis } from '@kbn/visualizations-plugin/public'; +import { Layer } from '..'; import { ChartType } from '../..'; import { CategoryAxis, @@ -96,28 +97,22 @@ function getSeriesType( } // percentage chart should be stacked - if (isPercentage || (mode === 'stacked' && seriesType !== SeriesTypes.LINE)) { - seriesType = (seriesType + '_stacked') as XYDataLayerConfig['seriesType']; + // line stacked should convert to area stacked + if (isPercentage || mode === 'stacked') { + seriesType = ((seriesType !== SeriesTypes.LINE ? seriesType : SeriesTypes.AREA) + + '_stacked') as XYDataLayerConfig['seriesType']; } return seriesType; } function getDataLayers( - layers: Array<{ - indexPatternId: string; - layerId: string; - columns: Column[]; - columnOrder: never[]; - seriesId: string; - isReferenceLineLayer: boolean; - collapseFn?: string; - }>, + layers: Layer[], series: SeriesParam[], - vis: Vis, - xColumn?: Column + vis: Vis ): XYDataLayerConfig[] { return layers.map((layer) => { + const xColumn = layer.columns.find((c) => c.isBucketed && !c.isSplit); const yAccessors = layer.columns.reduce((acc, column) => { if (!column.isBucketed) { acc.push(column.columnId); @@ -191,15 +186,7 @@ function getReferenceLineLayers( } export const getConfiguration = ( - layers: Array<{ - indexPatternId: string; - layerId: string; - columns: Column[]; - columnOrder: never[]; - seriesId: string; - isReferenceLineLayer: boolean; - collapseFn?: string; - }>, + layers: Layer[], series: SeriesParam[], vis: Vis ): XYConfiguration => { @@ -225,8 +212,7 @@ export const getConfiguration = ( ...getDataLayers( layers.filter((l) => !l.isReferenceLineLayer), series, - vis, - xColumn + vis ), ...getReferenceLineLayers( layers.filter((l) => l.isReferenceLineLayer), diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts index 2282b5229fde9..aa6c96d52d410 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Column, ColumnWithMeta } from '@kbn/visualizations-plugin/common'; +import { AggBasedColumn, Column, ColumnWithMeta } from '@kbn/visualizations-plugin/common'; import { convertToLensModule, getVisSchemas, @@ -18,6 +18,16 @@ import { getSeriesParams } from '../utils/get_series_params'; import { getConfiguration } from './configurations'; import { ConvertXYToLensVisualization } from './types'; +export interface Layer { + indexPatternId: string; + layerId: string; + columns: Column[]; + columnOrder: never[]; + seriesId: string; + isReferenceLineLayer: boolean; + collapseFn?: string; +} + export const isColumnWithMeta = (column: Column): column is ColumnWithMeta => { if ((column as ColumnWithMeta).meta) { return true; @@ -96,7 +106,7 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte const finalSeriesParams = updatedSeries ?? vis.params.seriesParams; const visibleSeries = finalSeriesParams.filter( - (param) => param.show && visSchemas.metric.some((m) => m.aggId === param.data.id) + (param) => param.show && visSchemas.metric.some((m) => m.aggId?.split('.')[0] === param.data.id) ); const visibleYAxes = vis.params.valueAxes.filter((axis) => @@ -113,11 +123,16 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte const indexPatternId = dataView.id!; - const layers = visibleSeries.map((s) => { + const layers = visibleSeries.reduce((accLayers, s) => { const layerId = uuid(); - const metrics = result.columns.filter((c) => c.meta.aggId === s.data.id); - const buckets = result.columns.filter((c) => { + const metrics = result.columns.filter((c) => c.meta.aggId.split('.')[0] === s.data.id); + const buckets = result.columns.reduce((acc, c) => { if (c.isBucketed) { + let bucketColumn = c; + // should change column id because each layer should includes its own columns (not use one column id for all layers) + if (accLayers.some((value) => value.columns.some((col) => col.columnId === c.columnId))) { + bucketColumn = { ...c, columnId: uuid() }; + } if (c.isSplit) { // as each sibling pipeline agg can have different custom bucket we should get correct one for layer if ( @@ -125,17 +140,22 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte result.buckets.customBuckets[key].includes(c.columnId) ) ) { - return result.buckets.customBuckets[metrics[0].columnId] === c.columnId; + if (result.buckets.customBuckets[metrics[0].columnId] === c.columnId) { + acc.push(bucketColumn); + } + return acc; } } - return true; + acc.push(bucketColumn); } - }); + + return acc; + }, []); const collapseFn = Object.keys(result.bucketCollapseFn).find((key) => result.bucketCollapseFn[key].includes(result.buckets.customBuckets[metrics[0].columnId]) ); - return { + accLayers.push({ indexPatternId, layerId, columns: [...metrics, ...buckets].map(excludeMetaFromColumn), @@ -143,8 +163,9 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte seriesId: s.data.id, collapseFn, isReferenceLineLayer: false, - }; - }); + }); + return accLayers; + }, []); if (vis.params.thresholdLine.show) { layers.push({ From d2c5ba776449eaedbf46ddf8113b36b73746a26a Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Fri, 7 Oct 2022 16:34:47 +0300 Subject: [PATCH 03/21] fixed bundle size --- .../xy/public/convert_to_lens/configurations/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts index ceb83b33e1abb..0fa9a25936098 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts @@ -7,13 +7,13 @@ */ import { Position, ScaleType as ECScaleType } from '@elastic/charts'; -import { +import type { Column, - SeriesTypes, XYConfiguration, XYDataLayerConfig, XYReferenceLineLayerConfig, -} from '@kbn/visualizations-plugin/common/convert_to_lens'; +} from '@kbn/visualizations-plugin/common/convert_to_lens/types'; +import { SeriesTypes } from '@kbn/visualizations-plugin/common/convert_to_lens/constants'; import { Vis } from '@kbn/visualizations-plugin/public'; import { Layer } from '..'; import { ChartType } from '../..'; From e759c1fd56afb64a3b7eefb1a25bcec5c1de8a23 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Fri, 7 Oct 2022 17:12:51 +0300 Subject: [PATCH 04/21] Fixed tests --- .../configurations/index.test.ts | 4 +- .../public/convert_to_lens/index.test.ts | 6 +- .../configurations/index.test.ts | 8 +-- .../pie/public/convert_to_lens/index.test.ts | 6 +- .../configurations/index.test.ts | 4 +- .../public/convert_to_lens/index.test.ts | 4 +- .../convert_to_lens/configurations/index.ts | 6 +- .../vis_types/xy/public/utils/common.ts | 2 +- .../public/convert_to_lens/schemas.test.ts | 15 ++++- .../public/convert_to_lens/schemas.ts | 4 +- .../public/convert_to_lens/utils.test.ts | 59 ++++++++++++++++--- .../public/convert_to_lens/utils.ts | 8 +-- 12 files changed, 88 insertions(+), 38 deletions(-) diff --git a/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.test.ts b/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.test.ts index 97fb145e74a2f..363e2374e3cec 100644 --- a/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.test.ts +++ b/src/plugins/vis_types/metric/public/convert_to_lens/configurations/index.test.ts @@ -66,9 +66,9 @@ describe('getConfiguration', () => { expect( getConfiguration(layerId, params, { metrics: [metric], - buckets: [bucket], + buckets: { all: [bucket], customBuckets: { metric: bucket } }, columnsWithoutReferenced: [], - bucketCollapseFn: { [metric]: collapseFn }, + bucketCollapseFn: { [collapseFn]: [bucket] }, }) ).toEqual({ breakdownByAccessor: bucket, diff --git a/src/plugins/vis_types/metric/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/metric/public/convert_to_lens/index.test.ts index 015b19157e91a..353dc4144b847 100644 --- a/src/plugins/vis_types/metric/public/convert_to_lens/index.test.ts +++ b/src/plugins/vis_types/metric/public/convert_to_lens/index.test.ts @@ -92,7 +92,7 @@ describe('convertToLens', () => { test('should return null if buckets count is more than 1', async () => { mockGetColumnsFromVis.mockReturnValue({ metrics: [], - buckets: ['1', '2'], + buckets: { all: ['1', '2'] }, columns: [{ columnId: '2' }, { columnId: '1' }], }); const result = await convertToLens(vis, timefilter); @@ -103,7 +103,7 @@ describe('convertToLens', () => { test('should return null if metric column data type is different from number', async () => { mockGetColumnsFromVis.mockReturnValue({ metrics: ['1'], - buckets: ['2'], + buckets: { all: ['2'] }, columns: [{ columnId: '2' }, { columnId: '1', dataType: 'string' }], }); const result = await convertToLens(vis, timefilter); @@ -118,7 +118,7 @@ describe('convertToLens', () => { mockGetColumnsFromVis.mockReturnValue({ metrics: ['1'], - buckets: ['2'], + buckets: { all: ['2'] }, columns: [{ columnId: '2' }, { columnId: '1', dataType: 'number' }], columnsWithoutReferenced: [ { columnId: '1', meta: { aggId: 'agg-1' } }, diff --git a/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.test.ts b/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.test.ts index 0a10a5bd7c0c0..87ec0d3b57b3f 100644 --- a/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.test.ts +++ b/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.test.ts @@ -19,7 +19,7 @@ describe('getConfiguration', () => { expect( getConfiguration('test1', samplePieVis as any, { metrics: ['metric-1'], - buckets: ['bucket-1'], + buckets: { all: ['bucket-1'], customBuckets: {} }, }) ).toEqual({ layers: [ @@ -55,7 +55,7 @@ describe('getConfiguration', () => { { ...samplePieVis, params: { ...samplePieVis.params, legendDisplay: 'hide' } } as any, { metrics: ['metric-1'], - buckets: ['bucket-1'], + buckets: { all: ['bucket-1'], customBuckets: {} }, } ) ).toEqual({ @@ -73,7 +73,7 @@ describe('getConfiguration', () => { { ...samplePieVis, params: { ...samplePieVis.params, legendDisplay: 'show' } } as any, { metrics: ['metric-1'], - buckets: ['bucket-1'], + buckets: { all: ['bucket-1'], customBuckets: {} }, } ) ).toEqual({ @@ -92,7 +92,7 @@ describe('getConfiguration', () => { { ...samplePieVis, params: { ...samplePieVis.params, legendDisplay } } as any, { metrics: ['metric-1'], - buckets: ['bucket-1'], + buckets: { all: ['bucket-1'], customBuckets: {} }, } ) ).toEqual({ diff --git a/src/plugins/vis_types/pie/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/pie/public/convert_to_lens/index.test.ts index c1e39d741f84d..2fca4f5349f91 100644 --- a/src/plugins/vis_types/pie/public/convert_to_lens/index.test.ts +++ b/src/plugins/vis_types/pie/public/convert_to_lens/index.test.ts @@ -41,7 +41,7 @@ describe('convertToLens', () => { test('should return null if more than three split slice levels', async () => { mockGetColumnsFromVis.mockReturnValue({ - buckets: ['1', '2', '3', '4'], + buckets: { all: ['1', '2', '3', '4'] }, }); const result = await convertToLens(samplePieVis as any, {} as any); expect(mockGetColumnsFromVis).toBeCalledTimes(1); @@ -50,7 +50,7 @@ describe('convertToLens', () => { test('should return null if no one split slices', async () => { mockGetColumnsFromVis.mockReturnValue({ - buckets: [], + buckets: { all: [] }, }); const result = await convertToLens(samplePieVis as any, {} as any); expect(mockGetColumnsFromVis).toBeCalledTimes(1); @@ -59,7 +59,7 @@ describe('convertToLens', () => { test('should state for valid vis', async () => { mockGetColumnsFromVis.mockReturnValue({ - buckets: ['2'], + buckets: { all: ['2'] }, columns: [{ columnId: '2' }, { columnId: '1' }], }); const result = await convertToLens(samplePieVis as any, {} as any); diff --git a/src/plugins/vis_types/table/public/convert_to_lens/configurations/index.test.ts b/src/plugins/vis_types/table/public/convert_to_lens/configurations/index.test.ts index 4393ec86c2271..d2e68436700d3 100644 --- a/src/plugins/vis_types/table/public/convert_to_lens/configurations/index.test.ts +++ b/src/plugins/vis_types/table/public/convert_to_lens/configurations/index.test.ts @@ -25,7 +25,7 @@ describe('getConfiguration', () => { expect( getConfiguration('test1', params, { metrics: ['metric-1'], - buckets: ['bucket-1'], + buckets: { all: ['bucket-1'], customBuckets: { 'metric-1': 'bucket-1' } }, columnsWithoutReferenced: [ { columnId: 'metric-1', @@ -48,7 +48,7 @@ describe('getConfiguration', () => { }, }, ], - bucketCollapseFn: { 'bucket-1': 'sum' }, + bucketCollapseFn: { sum: ['bucket-1'] }, }) ).toEqual({ columns: [ diff --git a/src/plugins/vis_types/table/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/table/public/convert_to_lens/index.test.ts index 5c1ad0578be11..75f7f48e8c41a 100644 --- a/src/plugins/vis_types/table/public/convert_to_lens/index.test.ts +++ b/src/plugins/vis_types/table/public/convert_to_lens/index.test.ts @@ -64,7 +64,7 @@ describe('convertToLens', () => { test('should return null if can not build percentage column', async () => { mockGetColumnsFromVis.mockReturnValue({ - buckets: ['2'], + buckets: { all: ['2'] }, columns: [{ columnId: '2' }, { columnId: '1' }], columnsWithoutReferenced: [ { columnId: '1', meta: { aggId: 'agg-1' } }, @@ -84,7 +84,7 @@ describe('convertToLens', () => { test('should return correct state for valid vis', async () => { mockGetColumnsFromVis.mockReturnValue({ - buckets: ['2'], + buckets: { all: ['2'] }, columns: [{ columnId: '2' }, { columnId: '1' }], columnsWithoutReferenced: [ { columnId: '1', meta: { aggId: 'agg-1' } }, diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts index 0fa9a25936098..e8323226b4a1b 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts @@ -7,13 +7,13 @@ */ import { Position, ScaleType as ECScaleType } from '@elastic/charts'; -import type { +import { + SeriesTypes, Column, XYConfiguration, XYDataLayerConfig, XYReferenceLineLayerConfig, -} from '@kbn/visualizations-plugin/common/convert_to_lens/types'; -import { SeriesTypes } from '@kbn/visualizations-plugin/common/convert_to_lens/constants'; +} from '@kbn/visualizations-plugin/common/convert_to_lens'; import { Vis } from '@kbn/visualizations-plugin/public'; import { Layer } from '..'; import { ChartType } from '../..'; diff --git a/src/plugins/vis_types/xy/public/utils/common.ts b/src/plugins/vis_types/xy/public/utils/common.ts index 71d9ab3ff4209..0c44c6509739e 100644 --- a/src/plugins/vis_types/xy/public/utils/common.ts +++ b/src/plugins/vis_types/xy/public/utils/common.ts @@ -7,7 +7,7 @@ */ import { Position } from '@elastic/charts'; -import { AxisExtentConfig } from '@kbn/visualizations-plugin/common/convert_to_lens'; +import type { AxisExtentConfig } from '@kbn/visualizations-plugin/common/convert_to_lens'; import { InterpolationMode, Scale, ThresholdLine } from '../types'; export interface Bounds { 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 5b8b7832730b9..95d4df3175cb3 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts @@ -21,6 +21,7 @@ import { getColumnsFromVis } from './schemas'; const mockConvertMetricToColumns = jest.fn(); const mockConvertBucketToColumns = jest.fn(); const mockGetCutomBucketsFromSiblingAggs = jest.fn(); +const mockGetCustomBucketColumns = jest.fn(); const mockGetVisSchemas = jest.fn(); const mockGetBucketCollapseFn = jest.fn(); @@ -55,6 +56,7 @@ jest.mock('./utils', () => ({ getMetricsWithoutDuplicates: jest.fn(() => mockGetMetricsWithoutDuplicates()), isValidVis: jest.fn(() => mockIsValidVis()), sortColumns: jest.fn(() => mockSortColumns()), + getCustomBucketColumns: jest.fn(() => mockGetCustomBucketColumns()), })); describe('getColumnsFromVis', () => { @@ -73,6 +75,7 @@ describe('getColumnsFromVis', () => { jest.clearAllMocks(); mockGetVisSchemas.mockReturnValue({}); mockIsValidVis.mockReturnValue(true); + mockGetCustomBucketColumns.mockReturnValue({ customBucketColumns: [], customBucketsMap: {} }); }); test('should return null if vis is not valid', () => { @@ -107,7 +110,10 @@ describe('getColumnsFromVis', () => { test('should return null if one sibling agg was provided and it is not supported', () => { const buckets: AggConfig[] = [aggConfig]; mockGetCutomBucketsFromSiblingAggs.mockReturnValue(buckets); - mockConvertBucketToColumns.mockReturnValue(null); + mockGetCustomBucketColumns.mockReturnValue({ + customBucketColumns: [null], + customBucketsMap: {}, + }); mockGetMetricsWithoutDuplicates.mockReturnValue([{}]); const result = getColumnsFromVis(vis, dataServiceMock.query.timefilter.timefilter, dataView, { @@ -120,7 +126,7 @@ describe('getColumnsFromVis', () => { expect(mockIsValidVis).toBeCalledTimes(1); expect(mockGetCutomBucketsFromSiblingAggs).toBeCalledTimes(1); expect(mockGetMetricsWithoutDuplicates).toBeCalledTimes(1); - expect(mockConvertBucketToColumns).toBeCalledTimes(1); + expect(mockGetCustomBucketColumns).toBeCalledTimes(1); expect(mockGetBucketColumns).toBeCalledTimes(0); }); @@ -240,7 +246,10 @@ describe('getColumnsFromVis', () => { expect(result).toEqual({ bucketCollapseFn, - buckets: [bucketId], + buckets: { + all: [bucketId], + customBuckets: {}, + }, columns: [...metrics, ...buckets], columnsWithoutReferenced, metrics: [metricId], diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.ts index 87e06abf803d4..88d8c7b646298 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.ts @@ -17,7 +17,7 @@ import { getVisSchemas, Schemas } from '../vis_schemas'; import { getBucketCollapseFn, getBucketColumns, - getCustomBuckets, + getCustomBucketColumns, getColumnIds, getColumnsWithoutReferenced, getMetricsWithoutDuplicates, @@ -84,7 +84,7 @@ export const getColumnsFromVis = ( } const metrics = metricColumns as AggBasedColumn[]; - const { customBucketColumns, customBucketsMap } = getCustomBuckets( + const { customBucketColumns, customBucketsMap } = getCustomBucketColumns( customBucketsWithMetricIds, metrics, dataView, 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 734a250a2972c..e978d057b9f4c 100644 --- a/src/plugins/visualizations/public/convert_to_lens/utils.test.ts +++ b/src/plugins/visualizations/public/convert_to_lens/utils.test.ts @@ -104,6 +104,7 @@ describe('getColumnsWithoutReferenced', () => { describe('getBucketCollapseFn', () => { const metric1: SchemaConfig = { accessor: 0, + aggId: '1', label: '', format: { id: undefined, @@ -115,21 +116,25 @@ describe('getBucketCollapseFn', () => { const metric2: SchemaConfig = { ...metric1, + aggId: '2', aggType: METRIC_TYPES.AVG_BUCKET, }; const metric3: SchemaConfig = { ...metric1, + aggId: '3', aggType: METRIC_TYPES.MAX_BUCKET, }; const metric4: SchemaConfig = { ...metric1, + aggId: '4', aggType: METRIC_TYPES.MIN_BUCKET, }; const metric5: SchemaConfig = { ...metric1, + aggId: '5', aggType: METRIC_TYPES.SUM_BUCKET, }; @@ -151,18 +156,54 @@ describe('getBucketCollapseFn', () => { test.each< [ string, - [Array>, AggBasedColumn[]], - Record + [ + Array>, + AggBasedColumn[], + Record, + AggBasedColumn[] + ], + Record ] >([ - ['avg', [[metric1, metric2], [customBucketColum]], { [customBucketColum.columnId]: 'avg' }], - ['max', [[metric1, metric3], [customBucketColum]], { [customBucketColum.columnId]: 'max' }], - ['min', [[metric1, metric4], [customBucketColum]], { [customBucketColum.columnId]: 'min' }], - ['sum', [[metric1, metric5], [customBucketColum]], { [customBucketColum.columnId]: 'sum' }], [ - 'undefined if no sibling pipeline agg is provided', - [[metric1], [customBucketColum]], - { [customBucketColum.columnId]: undefined }, + 'avg', + [ + [metric1, metric2], + [customBucketColum], + { test: 'bucket-1' }, + [{ columnId: 'test', meta: { aggId: metric2.aggId } } as AggBasedColumn], + ], + { sum: [], min: [], max: [], avg: [customBucketColum.columnId] }, + ], + [ + 'max', + [ + [metric1, metric3], + [customBucketColum], + { test: 'bucket-1' }, + [{ columnId: 'test', meta: { aggId: metric3.aggId } } as AggBasedColumn], + ], + { sum: [], min: [], max: [customBucketColum.columnId], avg: [] }, + ], + [ + 'min', + [ + [metric1, metric4], + [customBucketColum], + { test: 'bucket-1' }, + [{ columnId: 'test', meta: { aggId: metric4.aggId } } as AggBasedColumn], + ], + { sum: [], min: [customBucketColum.columnId], max: [], avg: [] }, + ], + [ + 'sum', + [ + [metric1, metric5], + [customBucketColum], + { test: 'bucket-1' }, + [{ columnId: 'test', meta: { aggId: metric5.aggId } } as AggBasedColumn], + ], + { sum: [customBucketColum.columnId], min: [], max: [], avg: [] }, ], ])('should return%s', (_, input, expected) => { expect(getBucketCollapseFn(...input)).toEqual(expected); diff --git a/src/plugins/visualizations/public/convert_to_lens/utils.ts b/src/plugins/visualizations/public/convert_to_lens/utils.ts index 8ec04852834a4..ac56501db522f 100644 --- a/src/plugins/visualizations/public/convert_to_lens/utils.ts +++ b/src/plugins/visualizations/public/convert_to_lens/utils.ts @@ -143,7 +143,7 @@ export const sortColumns = ( export const getColumnIds = (columns: AggBasedColumn[]) => columns.map(({ columnId }) => columnId); -export const getCustomBuckets = ( +export const getCustomBucketColumns = ( customBucketsWithMetricIds: Array<{ customBucket: IAggConfig; metricIds: string[]; @@ -164,11 +164,11 @@ export const getCustomBuckets = ( customBucketColumns.push(customBucketColumn); if (customBucketColumn) { customBucketWithMetricIds.metricIds.forEach((metricAggId) => { - const columnId = metricColumns.find( + const metricColumnId = metricColumns.find( (metricColumn) => metricColumn?.meta.aggId === metricAggId )?.columnId; - if (columnId) { - customBucketsMap[columnId] = customBucketColumn.columnId; + if (metricColumnId) { + customBucketsMap[metricColumnId] = customBucketColumn.columnId; } }); } From 9f4d247c6e93008d847a5bb3da31fd42945f63c4 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Mon, 10 Oct 2022 11:47:15 +0300 Subject: [PATCH 05/21] Fixed tests, decreased bundle size --- .../common/expression_functions/layered_xy_vis.test.ts | 2 +- .../common/expression_functions/xy_vis.test.ts | 2 ++ .../expression_xy/common/types/expression_renderers.ts | 2 +- .../expression_xy/public/components/xy_chart.tsx | 2 +- .../public/expression_renderers/xy_chart_renderer.tsx | 2 +- .../xy/public/convert_to_lens/configurations/index.ts | 2 +- .../vis_types/xy/public/convert_to_lens/index.ts | 8 +++++--- .../common/convert_to_lens/lib/utils.test.ts | 10 +++++----- .../visualizations/common/convert_to_lens/utils.ts | 6 +++--- 9 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.test.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.test.ts index 79427cbe4d3cc..27529c6291c8a 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.test.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.test.ts @@ -24,7 +24,7 @@ describe('layeredXyVis', () => { expect(result).toEqual({ type: 'render', as: XY_VIS, - value: { args: { ...rest, layers: [sampleExtendedLayer] } }, + value: { args: { ...rest, layers: [sampleExtendedLayer] }, canNavigateToLens: false }, }); }); diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts index 7e6afa0dd23a7..da5ebc8a6f6e8 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts @@ -38,6 +38,7 @@ describe('xyVis', () => { }, ], }, + canNavigateToLens: false, }, }); }); @@ -344,6 +345,7 @@ describe('xyVis', () => { }, ], }, + canNavigateToLens: false, }, }); }); diff --git a/src/plugins/chart_expressions/expression_xy/common/types/expression_renderers.ts b/src/plugins/chart_expressions/expression_xy/common/types/expression_renderers.ts index cad328b444443..480d2cdad31fb 100644 --- a/src/plugins/chart_expressions/expression_xy/common/types/expression_renderers.ts +++ b/src/plugins/chart_expressions/expression_xy/common/types/expression_renderers.ts @@ -16,7 +16,7 @@ import { XYProps } from './expression_functions'; export interface XYChartProps { args: XYProps; - canNavigateToLens: boolean; + canNavigateToLens?: boolean; } export interface XYRender { diff --git a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx index 45eca06c670b0..0d88480b00342 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx @@ -110,7 +110,7 @@ declare global { } } -export type XYChartRenderProps = XYChartProps & { +export type XYChartRenderProps = Omit & { chartsThemeService: ChartsPluginSetup['theme']; chartsActiveCursorService: ChartsPluginStart['activeCursor']; data: DataPublicPluginStart; diff --git a/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx b/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx index c3a91894408cc..bf708748eb6c5 100644 --- a/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx @@ -193,7 +193,7 @@ export const getXyChartRenderer = ({ const uiEvents = extractCounterEvents( visualizationType, config.args, - config.canNavigateToLens, + Boolean(config.canNavigateToLens), { getDataLayers, } diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts index e8323226b4a1b..f449d2d2ad6c8 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts @@ -16,7 +16,7 @@ import { } from '@kbn/visualizations-plugin/common/convert_to_lens'; import { Vis } from '@kbn/visualizations-plugin/public'; import { Layer } from '..'; -import { ChartType } from '../..'; +import { ChartType } from '../../../common'; import { CategoryAxis, ChartMode, diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts index aa6c96d52d410..1a3027a7838ef 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts @@ -12,10 +12,9 @@ import { getVisSchemas, getDataViewByIndexPatternId, } from '@kbn/visualizations-plugin/public'; -import uuid from 'uuid'; +import uuid from 'uuid/v4'; import { getDataViewsStart } from '../services'; import { getSeriesParams } from '../utils/get_series_params'; -import { getConfiguration } from './configurations'; import { ConvertXYToLensVisualization } from './types'; export interface Layer { @@ -55,7 +54,10 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte return null; } - const { getColumnsFromVis, createStaticValueColumn } = await convertToLensModule; + const [{ getColumnsFromVis, createStaticValueColumn }, { getConfiguration }] = await Promise.all([ + convertToLensModule, + import('./configurations'), + ]); const result = getColumnsFromVis( vis, timefilter, diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/utils.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/utils.test.ts index 73118b6ad4f03..1f91201aff503 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/utils.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/utils.test.ts @@ -365,7 +365,7 @@ describe('getCustomBucketsFromSiblingAggs', () => { }, params: {}, aggType: METRIC_TYPES.AVG_BUCKET, - aggId: 'some-agg-id', + aggId: 'some-agg-id-1', aggParams: { customBucket: bucketWithSerialize1, }, @@ -381,7 +381,7 @@ describe('getCustomBucketsFromSiblingAggs', () => { }, params: {}, aggType: METRIC_TYPES.AVG_BUCKET, - aggId: 'some-agg-id', + aggId: 'some-agg-id-2', aggParams: { customBucket: bucketWithSerialize2, }, @@ -399,7 +399,7 @@ describe('getCustomBucketsFromSiblingAggs', () => { }, params: {}, aggType: METRIC_TYPES.AVG_BUCKET, - aggId: 'some-agg-id', + aggId: 'some-agg-id-3', aggParams: { customBucket: bucketWithSerialize3, }, @@ -407,8 +407,8 @@ describe('getCustomBucketsFromSiblingAggs', () => { test("should filter out duplicated custom buckets, ignoring id's", () => { expect(getCustomBucketsFromSiblingAggs([metric1, metric2, metric3])).toEqual([ - bucketWithSerialize1, - bucketWithSerialize2, + { customBucket: bucketWithSerialize1, metricIds: ['some-agg-id-1', 'some-agg-id-3'] }, + { customBucket: bucketWithSerialize2, metricIds: ['some-agg-id-2'] }, ]); }); }); diff --git a/src/plugins/visualizations/common/convert_to_lens/utils.ts b/src/plugins/visualizations/common/convert_to_lens/utils.ts index 3536282d830d3..51c7e8239a439 100644 --- a/src/plugins/visualizations/common/convert_to_lens/utils.ts +++ b/src/plugins/visualizations/common/convert_to_lens/utils.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { DataViewField } from '@kbn/data-views-plugin/common'; -import { SupportedMetric } from './lib/convert/supported_metrics'; -import { Layer, XYAnnotationsLayerConfig, XYLayerConfig } from './types'; +import type { DataViewField } from '@kbn/data-views-plugin/common'; +import type { SupportedMetric } from './lib/convert/supported_metrics'; +import type { Layer, XYAnnotationsLayerConfig, XYLayerConfig } from './types'; export const isAnnotationsLayer = ( layer: Pick From 30112f0cefc1db4c25ee73eebda41b5b37221b68 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Mon, 10 Oct 2022 14:52:10 +0300 Subject: [PATCH 06/21] Add possibility to return several layers from getColumnsFromVis --- .../public/convert_to_lens/index.test.ts | 54 +++--- .../metric/public/convert_to_lens/index.ts | 6 +- .../pie/public/convert_to_lens/index.test.ts | 26 +-- .../pie/public/convert_to_lens/index.ts | 6 +- .../public/convert_to_lens/index.test.ts | 36 ++-- .../table/public/convert_to_lens/index.ts | 6 +- .../xy/public/convert_to_lens/index.ts | 96 +++++------ .../public/convert_to_lens/schemas.test.ts | 102 ++++++++++-- .../public/convert_to_lens/schemas.ts | 155 +++++++++++++----- 9 files changed, 320 insertions(+), 167 deletions(-) diff --git a/src/plugins/vis_types/metric/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/metric/public/convert_to_lens/index.test.ts index 353dc4144b847..44ff53b8366d9 100644 --- a/src/plugins/vis_types/metric/public/convert_to_lens/index.test.ts +++ b/src/plugins/vis_types/metric/public/convert_to_lens/index.test.ts @@ -81,31 +81,37 @@ describe('convertToLens', () => { }); test('should return null if metrics count is more than 1', async () => { - mockGetColumnsFromVis.mockReturnValue({ - metrics: ['1', '2'], - columns: [{ columnId: '2' }, { columnId: '1' }], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1', '2'], + columns: [{ columnId: '2' }, { columnId: '1' }], + }, + ]); const result = await convertToLens(vis, timefilter); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(result).toBeNull(); }); test('should return null if buckets count is more than 1', async () => { - mockGetColumnsFromVis.mockReturnValue({ - metrics: [], - buckets: { all: ['1', '2'] }, - columns: [{ columnId: '2' }, { columnId: '1' }], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: [], + buckets: { all: ['1', '2'] }, + columns: [{ columnId: '2' }, { columnId: '1' }], + }, + ]); const result = await convertToLens(vis, timefilter); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(result).toBeNull(); }); test('should return null if metric column data type is different from number', async () => { - mockGetColumnsFromVis.mockReturnValue({ - metrics: ['1'], - buckets: { all: ['2'] }, - columns: [{ columnId: '2' }, { columnId: '1', dataType: 'string' }], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1'], + buckets: { all: ['2'] }, + columns: [{ columnId: '2' }, { columnId: '1', dataType: 'string' }], + }, + ]); const result = await convertToLens(vis, timefilter); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(result).toBeNull(); @@ -116,15 +122,17 @@ describe('convertToLens', () => { metricAccessor: '1', }; - mockGetColumnsFromVis.mockReturnValue({ - metrics: ['1'], - buckets: { all: ['2'] }, - columns: [{ columnId: '2' }, { columnId: '1', dataType: 'number' }], - columnsWithoutReferenced: [ - { columnId: '1', meta: { aggId: 'agg-1' } }, - { columnId: '2', meta: { aggId: 'agg-2' } }, - ], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1'], + buckets: { all: ['2'] }, + columns: [{ columnId: '2' }, { 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); diff --git a/src/plugins/vis_types/metric/public/convert_to_lens/index.ts b/src/plugins/vis_types/metric/public/convert_to_lens/index.ts index 1c87aa6aa3903..e44c71123685c 100644 --- a/src/plugins/vis_types/metric/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/metric/public/convert_to_lens/index.ts @@ -47,7 +47,7 @@ export const convertToLens: ConvertMetricVisToLensVisualization = async (vis, ti import('./configurations'), ]); - const result = getColumnsFromVis( + const layers = getColumnsFromVis( vis, timefilter, dataView, @@ -57,10 +57,12 @@ export const convertToLens: ConvertMetricVisToLensVisualization = async (vis, ti { dropEmptyRowsInDateHistogram: true, ...getPercentageModeConfig(vis.params) } ); - if (result === null) { + if (layers === null) { return null; } + const result = layers[0]; + // for now, multiple metrics are not supported if (result.metrics.length > 1 || result.buckets.all.length > 1) { return null; diff --git a/src/plugins/vis_types/pie/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/pie/public/convert_to_lens/index.test.ts index 2fca4f5349f91..bf4dfbd3ffc72 100644 --- a/src/plugins/vis_types/pie/public/convert_to_lens/index.test.ts +++ b/src/plugins/vis_types/pie/public/convert_to_lens/index.test.ts @@ -40,28 +40,34 @@ describe('convertToLens', () => { }); test('should return null if more than three split slice levels', async () => { - mockGetColumnsFromVis.mockReturnValue({ - buckets: { all: ['1', '2', '3', '4'] }, - }); + mockGetColumnsFromVis.mockReturnValue([ + { + buckets: { all: ['1', '2', '3', '4'] }, + }, + ]); const result = await convertToLens(samplePieVis as any, {} as any); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(result).toBeNull(); }); test('should return null if no one split slices', async () => { - mockGetColumnsFromVis.mockReturnValue({ - buckets: { all: [] }, - }); + mockGetColumnsFromVis.mockReturnValue([ + { + buckets: { all: [] }, + }, + ]); const result = await convertToLens(samplePieVis as any, {} as any); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(result).toBeNull(); }); test('should state for valid vis', async () => { - mockGetColumnsFromVis.mockReturnValue({ - buckets: { all: ['2'] }, - columns: [{ columnId: '2' }, { columnId: '1' }], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + buckets: { all: ['2'] }, + columns: [{ columnId: '2' }, { columnId: '1' }], + }, + ]); const result = await convertToLens(samplePieVis as any, {} as any); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(mockGetConfiguration).toBeCalledTimes(1); diff --git a/src/plugins/vis_types/pie/public/convert_to_lens/index.ts b/src/plugins/vis_types/pie/public/convert_to_lens/index.ts index 066918cb439af..ecfeee600d036 100644 --- a/src/plugins/vis_types/pie/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/pie/public/convert_to_lens/index.ts @@ -44,16 +44,18 @@ export const convertToLens: ConvertPieToLensVisualization = async (vis, timefilt } const { getColumnsFromVis } = await convertToLensModule; - const result = getColumnsFromVis(vis, timefilter, dataView, { + const layers = getColumnsFromVis(vis, timefilter, dataView, { buckets: [], splits: ['segment'], unsupported: ['split_row', 'split_column'], }); - if (result === null) { + if (layers === null) { return null; } + const result = layers[0]; + // doesn't support more than three split slice levels // doesn't support pie without at least one split slice if (result.buckets.all.length > 3 || !result.buckets.all.length) { diff --git a/src/plugins/vis_types/table/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/table/public/convert_to_lens/index.test.ts index 75f7f48e8c41a..f11f18f754eb9 100644 --- a/src/plugins/vis_types/table/public/convert_to_lens/index.test.ts +++ b/src/plugins/vis_types/table/public/convert_to_lens/index.test.ts @@ -63,14 +63,16 @@ describe('convertToLens', () => { }); test('should return null if can not build percentage column', async () => { - mockGetColumnsFromVis.mockReturnValue({ - buckets: { all: ['2'] }, - columns: [{ columnId: '2' }, { columnId: '1' }], - columnsWithoutReferenced: [ - { columnId: '1', meta: { aggId: 'agg-1' } }, - { columnId: '2', meta: { aggId: 'agg-2' } }, - ], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + buckets: { all: ['2'] }, + columns: [{ columnId: '2' }, { columnId: '1' }], + columnsWithoutReferenced: [ + { columnId: '1', meta: { aggId: 'agg-1' } }, + { columnId: '2', meta: { aggId: 'agg-2' } }, + ], + }, + ]); mockGetVisSchemas.mockReturnValue({ metric: [{ label: 'Count', aggId: 'agg-1' }], }); @@ -83,14 +85,16 @@ describe('convertToLens', () => { }); test('should return correct state for valid vis', async () => { - mockGetColumnsFromVis.mockReturnValue({ - buckets: { all: ['2'] }, - columns: [{ columnId: '2' }, { columnId: '1' }], - columnsWithoutReferenced: [ - { columnId: '1', meta: { aggId: 'agg-1' } }, - { columnId: '2', meta: { aggId: 'agg-2' } }, - ], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + buckets: { all: ['2'] }, + columns: [{ columnId: '2' }, { columnId: '1' }], + columnsWithoutReferenced: [ + { columnId: '1', meta: { aggId: 'agg-1' } }, + { columnId: '2', meta: { aggId: 'agg-2' } }, + ], + }, + ]); mockGetVisSchemas.mockReturnValue({ metric: [{ label: 'Count', aggId: 'agg-1' }], }); 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 1b37e36f1d982..6ef3ab53250cf 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 @@ -46,7 +46,7 @@ export const convertToLens: ConvertTableToLensVisualization = async (vis, timefi } const { getColumnsFromVis, getPercentageColumnFormulaColumn } = await convertToLensModule; - const result = getColumnsFromVis( + const layers = getColumnsFromVis( vis, timefilter, dataView, @@ -57,10 +57,12 @@ export const convertToLens: ConvertTableToLensVisualization = async (vis, timefi { dropEmptyRowsInDateHistogram: true, isPercentageMode: false } ); - if (result === null) { + if (layers === null) { return null; } + const result = layers[0]; + if (vis.params.percentageCol) { const visSchemas = getVisSchemas(vis, { timefilter, diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts index 1a3027a7838ef..0f3ebe1ea2832 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { AggBasedColumn, Column, ColumnWithMeta } from '@kbn/visualizations-plugin/common'; +import { Column, ColumnWithMeta } from '@kbn/visualizations-plugin/common'; import { convertToLensModule, getVisSchemas, @@ -54,11 +54,29 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte return null; } + const visSchemas = getVisSchemas(vis, { + timefilter, + timeRange: timefilter.getAbsoluteTime(), + }); + + const firstValueAxesId = vis.params.valueAxes[0].id; + const updatedSeries = getSeriesParams( + vis.data.aggs, + vis.params.seriesParams, + 'metric', + firstValueAxesId + ); + + const finalSeriesParams = updatedSeries ?? vis.params.seriesParams; + const visibleSeries = finalSeriesParams.filter( + (param) => param.show && visSchemas.metric.some((m) => m.aggId?.split('.')[0] === param.data.id) + ); + const [{ getColumnsFromVis, createStaticValueColumn }, { getConfiguration }] = await Promise.all([ convertToLensModule, import('./configurations'), ]); - const result = getColumnsFromVis( + const dataLayers = getColumnsFromVis( vis, timefilter, dataView, @@ -71,46 +89,34 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte dropEmptyRowsInDateHistogram: true, supportMixedSiblingPipelineAggs: true, isPercentageMode: false, - } + }, + visibleSeries.map((s) => ({ metrics: [s.data.id] })) ); - if (result === null) { + if (dataLayers === null) { return null; } - const visSchemas = getVisSchemas(vis, { - timefilter, - timeRange: timefilter.getAbsoluteTime(), - }); - // doesn't support sibling pipeline aggs and split series together - if (visSchemas.group?.length && Object.keys(result.buckets.customBuckets).length) { + if ( + visSchemas.group?.length && + dataLayers.some((l) => Object.keys(l.buckets.customBuckets).length) + ) { return null; } // doesn't support several metrics with terms split series which uses one of the metrics as order agg if ( visSchemas.metric.length > 1 && - result.columns.some( - (c) => c.isSplit && 'orderBy' in c.params && c.params.orderBy.type === 'column' + dataLayers.some((l) => + l.columns.some( + (c) => c.isSplit && 'orderBy' in c.params && c.params.orderBy.type === 'column' + ) ) ) { return null; } - const firstValueAxesId = vis.params.valueAxes[0].id; - const updatedSeries = getSeriesParams( - vis.data.aggs, - vis.params.seriesParams, - 'metric', - firstValueAxesId - ); - - const finalSeriesParams = updatedSeries ?? vis.params.seriesParams; - const visibleSeries = finalSeriesParams.filter( - (param) => param.show && visSchemas.metric.some((m) => m.aggId?.split('.')[0] === param.data.id) - ); - const visibleYAxes = vis.params.valueAxes.filter((axis) => visibleSeries.some((seriesParam) => seriesParam.valueAxis === axis.id) ); @@ -125,44 +131,20 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte const indexPatternId = dataView.id!; - const layers = visibleSeries.reduce((accLayers, s) => { + const layers = dataLayers.reduce((accLayers, l) => { const layerId = uuid(); - const metrics = result.columns.filter((c) => c.meta.aggId.split('.')[0] === s.data.id); - const buckets = result.columns.reduce((acc, c) => { - if (c.isBucketed) { - let bucketColumn = c; - // should change column id because each layer should includes its own columns (not use one column id for all layers) - if (accLayers.some((value) => value.columns.some((col) => col.columnId === c.columnId))) { - bucketColumn = { ...c, columnId: uuid() }; - } - if (c.isSplit) { - // as each sibling pipeline agg can have different custom bucket we should get correct one for layer - if ( - Object.keys(result.buckets.customBuckets).some((key) => - result.buckets.customBuckets[key].includes(c.columnId) - ) - ) { - if (result.buckets.customBuckets[metrics[0].columnId] === c.columnId) { - acc.push(bucketColumn); - } - return acc; - } - } - - acc.push(bucketColumn); - } - - return acc; - }, []); - const collapseFn = Object.keys(result.bucketCollapseFn).find((key) => - result.bucketCollapseFn[key].includes(result.buckets.customBuckets[metrics[0].columnId]) + const series = visibleSeries.find((s) => + l.columns.some((c) => c.meta.aggId.split('.')[0] === s.data.id) + ); + const collapseFn = Object.keys(l.bucketCollapseFn).find((key) => + l.bucketCollapseFn[key].includes(l.buckets.customBuckets[l.metrics[0]]) ); accLayers.push({ indexPatternId, layerId, - columns: [...metrics, ...buckets].map(excludeMetaFromColumn), + columns: l.columns.map(excludeMetaFromColumn), columnOrder: [], - seriesId: s.data.id, + seriesId: series?.data.id!, collapseFn, isReferenceLineLayer: false, }); 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 95d4df3175cb3..54975d08b8486 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts @@ -196,7 +196,7 @@ describe('getColumnsFromVis', () => { expect(mockSortColumns).toBeCalledTimes(0); }); - test('should return columns', () => { + test('should return one layer with columns', () => { const buckets: AggConfig[] = [aggConfig]; const bucketColumns = [ { @@ -244,16 +244,18 @@ describe('getColumnsFromVis', () => { buckets: [], }); - expect(result).toEqual({ - bucketCollapseFn, - buckets: { - all: [bucketId], - customBuckets: {}, + expect(result).toEqual([ + { + bucketCollapseFn, + buckets: { + all: [bucketId], + customBuckets: {}, + }, + columns: [...metrics, ...buckets], + columnsWithoutReferenced, + metrics: [metricId], }, - columns: [...metrics, ...buckets], - columnsWithoutReferenced, - metrics: [metricId], - }); + ]); expect(mockGetVisSchemas).toBeCalledTimes(1); expect(mockIsValidVis).toBeCalledTimes(1); expect(mockGetCutomBucketsFromSiblingAggs).toBeCalledTimes(1); @@ -263,4 +265,84 @@ describe('getColumnsFromVis', () => { expect(mockSortColumns).toBeCalledTimes(1); expect(mockGetColumnsWithoutReferenced).toBeCalledTimes(1); }); + + test('should return several layer with columns if series is provided', () => { + const buckets: AggConfig[] = [aggConfig]; + const bucketColumns = [ + { + sourceField: 'some-field', + columnId: 'col3', + operationType: 'date_histogram', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: { interval: '1h' }, + meta: { aggId: 'agg-id-1' }, + }, + ]; + const mectricAggs = [{ aggId: 'col-id-3' }, { aggId: 'col-id-4' }]; + const metrics = [ + { + sourceField: 'some-field', + columnId: 'col2', + operationType: 'max', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: {}, + meta: { aggId: 'col-id-3' }, + }, + { + sourceField: 'some-field', + columnId: 'col3', + operationType: 'max', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: {}, + meta: { aggId: 'col-id-4' }, + }, + ]; + + const columnsWithoutReferenced = ['col2']; + const metricId = 'metric1'; + const bucketId = 'bucket1'; + const bucketCollapseFn = 'max'; + + mockGetCutomBucketsFromSiblingAggs.mockReturnValue([]); + mockGetMetricsWithoutDuplicates.mockReturnValue(mectricAggs); + mockConvertMetricToColumns.mockReturnValue(metrics); + mockConvertBucketToColumns.mockReturnValue(bucketColumns); + mockGetBucketColumns.mockReturnValue(bucketColumns); + mockGetColumnsWithoutReferenced.mockReturnValue(columnsWithoutReferenced); + mockSortColumns.mockReturnValue([...metrics, ...buckets]); + mockGetColumnIds.mockReturnValueOnce([metricId]); + mockGetColumnIds.mockReturnValueOnce([bucketId]); + mockGetColumnIds.mockReturnValueOnce([metricId]); + mockGetColumnIds.mockReturnValueOnce([bucketId]); + mockGetBucketCollapseFn.mockReturnValueOnce(bucketCollapseFn); + mockGetBucketCollapseFn.mockReturnValueOnce(bucketCollapseFn); + + const result = getColumnsFromVis( + vis, + dataServiceMock.query.timefilter.timefilter, + dataView, + { + splits: [], + buckets: [], + }, + undefined, + [{ metrics: ['col-id-3'] }, { metrics: ['col-id-4'] }] + ); + + expect(result?.length).toEqual(2); + expect(mockGetVisSchemas).toBeCalledTimes(1); + expect(mockIsValidVis).toBeCalledTimes(1); + expect(mockGetCutomBucketsFromSiblingAggs).toBeCalledTimes(1); + expect(mockGetMetricsWithoutDuplicates).toBeCalledTimes(1); + expect(mockConvertMetricToColumns).toBeCalledTimes(2); + expect(mockGetBucketColumns).toBeCalledTimes(4); + expect(mockSortColumns).toBeCalledTimes(2); + expect(mockGetColumnsWithoutReferenced).toBeCalledTimes(2); + }); }); diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.ts index 88d8c7b646298..884e8d5f42abb 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.ts @@ -7,7 +7,7 @@ */ import type { DataView } from '@kbn/data-views-plugin/common'; -import { METRIC_TYPES, TimefilterContract } from '@kbn/data-plugin/public'; +import { IAggConfig, METRIC_TYPES, TimefilterContract } from '@kbn/data-plugin/public'; import { AggBasedColumn, PercentageModeConfig, SchemaConfig } from '../../common'; import { convertMetricToColumns } from '../../common/convert_to_lens/lib/metrics'; import { getCustomBucketsFromSiblingAggs } from '../../common/convert_to_lens/lib/utils'; @@ -32,64 +32,39 @@ const areVisSchemasValid = (visSchemas: Schemas, unsupported: Array( - vis: Vis, - timefilter: TimefilterContract, +const createLayer = ( + visSchemas: Schemas, + allMetrics: Array>, + metricsForLayer: Array>, + customBucketsWithMetricIds: Array<{ + customBucket: IAggConfig; + metricIds: string[]; + }>, dataView: DataView, { splits = [], buckets = [], - unsupported = [], }: { splits?: Array; buckets?: Array; - unsupported?: Array; } = {}, - config?: { - dropEmptyRowsInDateHistogram?: boolean; - supportMixedSiblingPipelineAggs?: boolean; - } & (PercentageModeConfig | void) + percentageModeConfig: PercentageModeConfig, + dropEmptyRowsInDateHistogram?: boolean ) => { - const { dropEmptyRowsInDateHistogram, ...percentageModeConfig } = config ?? { - isPercentageMode: false, - }; - const visSchemas = getVisSchemas(vis, { - timefilter, - timeRange: timefilter.getAbsoluteTime(), - }); - - if ( - !isValidVis(visSchemas, config?.supportMixedSiblingPipelineAggs) || - !areVisSchemasValid(visSchemas, unsupported) - ) { - return null; - } - - const customBucketsWithMetricIds = getCustomBucketsFromSiblingAggs(visSchemas.metric); - - // doesn't support sibbling pipeline aggs with different bucket aggs - if (!config?.supportMixedSiblingPipelineAggs && customBucketsWithMetricIds.length > 1) { - return null; - } - - const metricsWithoutDuplicates = getMetricsWithoutDuplicates(visSchemas.metric); - const aggs = metricsWithoutDuplicates as Array>; - - const metricColumns = aggs.flatMap((m) => - convertMetricToColumns(m, dataView, aggs, percentageModeConfig) + const metricColumns = metricsForLayer.flatMap((m) => + convertMetricToColumns(m, dataView, allMetrics, percentageModeConfig) ); - if (metricColumns.includes(null)) { return null; } - const metrics = metricColumns as AggBasedColumn[]; + const metricColumnsWithoutNull = metricColumns as AggBasedColumn[]; const { customBucketColumns, customBucketsMap } = getCustomBucketColumns( customBucketsWithMetricIds, - metrics, + metricColumnsWithoutNull, dataView, - aggs, - config?.dropEmptyRowsInDateHistogram + allMetrics, + dropEmptyRowsInDateHistogram ); if (customBucketColumns.includes(null)) { @@ -122,14 +97,14 @@ export const getColumnsFromVis = ( const columns = sortColumns( [ - ...metrics, + ...metricColumnsWithoutNull, ...bucketColumns, ...splitBucketColumns, ...(customBucketColumns as BucketColumn[]), ], visSchemas, [...buckets, ...splits], - metricsWithoutDuplicates + metricsForLayer ); const columnsWithoutReferenced = getColumnsWithoutReferenced(columns); @@ -144,9 +119,99 @@ export const getColumnsFromVis = ( visSchemas.metric, customBucketColumns as BucketColumn[], customBucketsMap, - metrics + metricColumnsWithoutNull ), columnsWithoutReferenced, columns, }; }; + +export const getColumnsFromVis = ( + vis: Vis, + timefilter: TimefilterContract, + dataView: DataView, + { + splits = [], + buckets = [], + unsupported = [], + }: { + splits?: Array; + buckets?: Array; + unsupported?: Array; + } = {}, + config?: { + dropEmptyRowsInDateHistogram?: boolean; + supportMixedSiblingPipelineAggs?: boolean; + } & (PercentageModeConfig | void), + series?: Array<{ metrics: string[] }> +) => { + const { dropEmptyRowsInDateHistogram, supportMixedSiblingPipelineAggs, ...percentageModeConfig } = + config ?? { + isPercentageMode: false, + }; + const visSchemas = getVisSchemas(vis, { + timefilter, + timeRange: timefilter.getAbsoluteTime(), + }); + + if ( + !isValidVis(visSchemas, supportMixedSiblingPipelineAggs) || + !areVisSchemasValid(visSchemas, unsupported) + ) { + return null; + } + + const customBucketsWithMetricIds = getCustomBucketsFromSiblingAggs(visSchemas.metric); + + // doesn't support sibbling pipeline aggs with different bucket aggs + if (!supportMixedSiblingPipelineAggs && customBucketsWithMetricIds.length > 1) { + return null; + } + + const metricsWithoutDuplicates = getMetricsWithoutDuplicates(visSchemas.metric); + const aggs = metricsWithoutDuplicates as Array>; + const layers = []; + + if (series && series.length) { + for (let i = 0; i < series.length; i++) { + const metricAggIds = series[i].metrics; + const metrics = aggs.filter( + (agg) => agg.aggId && metricAggIds.includes(agg.aggId.split('.')[0]) + ); + const customBucketsForLayer = customBucketsWithMetricIds.filter((c) => + c.metricIds.some((m) => metricAggIds.includes(m)) + ); + const layer = createLayer( + visSchemas, + aggs, + metrics, + customBucketsForLayer, + dataView, + { splits, buckets }, + percentageModeConfig, + dropEmptyRowsInDateHistogram + ); + if (!layer) { + return null; + } + layers.push(layer); + } + } else { + const layer = createLayer( + visSchemas, + aggs, + aggs, + customBucketsWithMetricIds, + dataView, + { splits, buckets }, + percentageModeConfig, + dropEmptyRowsInDateHistogram + ); + if (!layer) { + return null; + } + layers.push(layer); + } + + return layers; +}; From a7f5258c409897540f79ccd976ab1239e5a38dce Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Mon, 10 Oct 2022 16:17:04 +0300 Subject: [PATCH 07/21] Some refactoring --- .../vis_types/xy/public/convert_to_lens/index.ts | 7 ++++--- src/plugins/vis_types/xy/public/to_ast.ts | 16 +++------------- src/plugins/vis_types/xy/public/utils/common.ts | 2 +- .../vis_types/xy/public/vis_types/area.ts | 12 ++---------- .../public/vis_types/get_vis_type_from_params.ts | 2 +- .../vis_types/xy/public/vis_types/histogram.ts | 12 ++---------- .../xy/public/vis_types/horizontal_bar.ts | 12 ++---------- .../vis_types/xy/public/vis_types/index.ts | 15 ++++++++++++++- .../vis_types/xy/public/vis_types/line.ts | 12 ++---------- 9 files changed, 31 insertions(+), 59 deletions(-) diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts index 0f3ebe1ea2832..46c23b6b97e43 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts @@ -12,7 +12,6 @@ import { getVisSchemas, getDataViewByIndexPatternId, } from '@kbn/visualizations-plugin/public'; -import uuid from 'uuid/v4'; import { getDataViewsStart } from '../services'; import { getSeriesParams } from '../utils/get_series_params'; import { ConvertXYToLensVisualization } from './types'; @@ -131,8 +130,10 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte const indexPatternId = dataView.id!; + const uuid = await import('uuid/v4'); + const layers = dataLayers.reduce((accLayers, l) => { - const layerId = uuid(); + const layerId = uuid.default(); const series = visibleSeries.find((s) => l.columns.some((c) => c.meta.aggId.split('.')[0] === s.data.id) ); @@ -154,7 +155,7 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte if (vis.params.thresholdLine.show) { layers.push({ indexPatternId, - layerId: uuid(), + layerId: uuid.default(), columns: [createStaticValueColumn(vis.params.thresholdLine.value || 0)], columnOrder: [], isReferenceLineLayer: true, diff --git a/src/plugins/vis_types/xy/public/to_ast.ts b/src/plugins/vis_types/xy/public/to_ast.ts index 95c0fc827685d..bd11e92d47dca 100644 --- a/src/plugins/vis_types/xy/public/to_ast.ts +++ b/src/plugins/vis_types/xy/public/to_ast.ts @@ -18,8 +18,8 @@ import { } from '@kbn/visualizations-plugin/public'; import { buildExpression, buildExpressionFunction } from '@kbn/expressions-plugin/public'; import { BUCKET_TYPES } from '@kbn/data-plugin/public'; -import { TimeRangeBounds } from '@kbn/data-plugin/common'; -import { PaletteOutput } from '@kbn/charts-plugin/common/expressions/palette/types'; +import type { TimeRangeBounds } from '@kbn/data-plugin/common'; +import type { PaletteOutput } from '@kbn/charts-plugin/common/expressions/palette/types'; import { Dimensions, Dimension, @@ -35,7 +35,7 @@ import { import { ChartType } from '../common'; import { getSeriesParams } from './utils/get_series_params'; import { getSafeId } from './utils/accessors'; -import { Bounds, getCurveType, getMode, getYAxisPosition } from './utils/common'; +import { Bounds, getCurveType, getLineStyle, getMode, getYAxisPosition } from './utils/common'; type YDimension = Omit & { accessor: string }; @@ -235,16 +235,6 @@ const prepareYAxis = (data: ValueAxis, showGridLines?: boolean) => { return buildExpression([yAxisConfig]); }; -const getLineStyle = (style: ThresholdLine['style']) => { - switch (style) { - case 'full': - return 'solid'; - case 'dashed': - case 'dot-dashed': - return style; - } -}; - const prepareReferenceLine = (thresholdLine: ThresholdLine, axisId: string) => { const referenceLine = buildExpressionFunction('referenceLine', { value: thresholdLine.value, diff --git a/src/plugins/vis_types/xy/public/utils/common.ts b/src/plugins/vis_types/xy/public/utils/common.ts index 0c44c6509739e..522cac2aa00ac 100644 --- a/src/plugins/vis_types/xy/public/utils/common.ts +++ b/src/plugins/vis_types/xy/public/utils/common.ts @@ -8,7 +8,7 @@ import { Position } from '@elastic/charts'; import type { AxisExtentConfig } from '@kbn/visualizations-plugin/common/convert_to_lens'; -import { InterpolationMode, Scale, ThresholdLine } from '../types'; +import type { InterpolationMode, Scale, ThresholdLine } from '../types'; export interface Bounds { min?: string | number; diff --git a/src/plugins/vis_types/xy/public/vis_types/area.ts b/src/plugins/vis_types/xy/public/vis_types/area.ts index bbb930ca0e4a1..0ca07c8067457 100644 --- a/src/plugins/vis_types/xy/public/vis_types/area.ts +++ b/src/plugins/vis_types/xy/public/vis_types/area.ts @@ -12,7 +12,7 @@ import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { Fit, Position } from '@elastic/charts'; import { AggGroupNames } from '@kbn/data-plugin/public'; -import { VisTypeDefinition, VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; +import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { defaultCountLabel, LabelRotation } from '@kbn/charts-plugin/public'; import { @@ -22,15 +22,13 @@ import { AxisMode, ThresholdLineStyle, InterpolationMode, - VisParams, } from '../types'; import { toExpressionAst } from '../to_ast'; import { ChartType } from '../../common'; import { optionTabs } from '../editor/common_config'; import { getVisTypeFromParams } from './get_vis_type_from_params'; -import { convertToLens } from '../convert_to_lens'; -export const areaVisTypeDefinition: VisTypeDefinition = { +export const areaVisTypeDefinition = { name: 'area', title: i18n.translate('visTypeXy.area.areaTitle', { defaultMessage: 'Area' }), icon: 'visArea', @@ -40,12 +38,6 @@ export const areaVisTypeDefinition: VisTypeDefinition = { fetchDatatable: true, toExpressionAst, getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], - navigateToLens: async (vis, timefilter) => (vis ? convertToLens(vis, timefilter) : null), - getExpressionVariables: async (vis, timeFilter) => { - return { - canNavigateToLens: Boolean(vis?.params ? await convertToLens(vis, timeFilter) : null), - }; - }, updateVisTypeOnParamsChange: getVisTypeFromParams, visConfig: { defaults: { diff --git a/src/plugins/vis_types/xy/public/vis_types/get_vis_type_from_params.ts b/src/plugins/vis_types/xy/public/vis_types/get_vis_type_from_params.ts index c1f79b041e807..1f7ead86843d9 100644 --- a/src/plugins/vis_types/xy/public/vis_types/get_vis_type_from_params.ts +++ b/src/plugins/vis_types/xy/public/vis_types/get_vis_type_from_params.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { VisParams } from '@kbn/visualizations-plugin/common'; +import type { VisParams } from '@kbn/visualizations-plugin/common'; export const getVisTypeFromParams = (params?: VisParams) => { let type = params?.seriesParams?.[0]?.type; diff --git a/src/plugins/vis_types/xy/public/vis_types/histogram.ts b/src/plugins/vis_types/xy/public/vis_types/histogram.ts index 20f3f18c68294..680186eb330f9 100644 --- a/src/plugins/vis_types/xy/public/vis_types/histogram.ts +++ b/src/plugins/vis_types/xy/public/vis_types/histogram.ts @@ -12,7 +12,7 @@ import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { Position } from '@elastic/charts'; import { AggGroupNames } from '@kbn/data-plugin/public'; -import { VisTypeDefinition, VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; +import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { defaultCountLabel, LabelRotation } from '@kbn/charts-plugin/public'; import { @@ -22,15 +22,13 @@ import { AxisMode, ThresholdLineStyle, InterpolationMode, - VisParams, } from '../types'; import { toExpressionAst } from '../to_ast'; import { ChartType } from '../../common'; import { optionTabs } from '../editor/common_config'; import { getVisTypeFromParams } from './get_vis_type_from_params'; -import { convertToLens } from '../convert_to_lens'; -export const histogramVisTypeDefinition: VisTypeDefinition = { +export const histogramVisTypeDefinition = { name: 'histogram', title: i18n.translate('visTypeXy.histogram.histogramTitle', { defaultMessage: 'Vertical bar', @@ -43,12 +41,6 @@ export const histogramVisTypeDefinition: VisTypeDefinition = { toExpressionAst, getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], updateVisTypeOnParamsChange: getVisTypeFromParams, - navigateToLens: async (vis, timefilter) => (vis ? convertToLens(vis, timefilter) : null), - getExpressionVariables: async (vis, timeFilter) => { - return { - canNavigateToLens: Boolean(vis?.params ? await convertToLens(vis, timeFilter) : null), - }; - }, visConfig: { defaults: { type: ChartType.Histogram, diff --git a/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts b/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts index fbc07410ea7fc..25fc3142e0e98 100644 --- a/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts +++ b/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts @@ -12,7 +12,7 @@ import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { Position } from '@elastic/charts'; import { AggGroupNames } from '@kbn/data-plugin/public'; -import { VisTypeDefinition, VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; +import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { defaultCountLabel, LabelRotation } from '@kbn/charts-plugin/public'; import { @@ -22,15 +22,13 @@ import { AxisMode, ThresholdLineStyle, InterpolationMode, - VisParams, } from '../types'; import { toExpressionAst } from '../to_ast'; import { ChartType } from '../../common'; import { optionTabs } from '../editor/common_config'; import { getVisTypeFromParams } from './get_vis_type_from_params'; -import { convertToLens } from '../convert_to_lens'; -export const horizontalBarVisTypeDefinition: VisTypeDefinition = { +export const horizontalBarVisTypeDefinition = { name: 'horizontal_bar', title: i18n.translate('visTypeXy.horizontalBar.horizontalBarTitle', { defaultMessage: 'Horizontal bar', @@ -42,12 +40,6 @@ export const horizontalBarVisTypeDefinition: VisTypeDefinition = { fetchDatatable: true, toExpressionAst, getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], - navigateToLens: async (vis, timefilter) => (vis ? convertToLens(vis, timefilter) : null), - getExpressionVariables: async (vis, timeFilter) => { - return { - canNavigateToLens: Boolean(vis?.params ? await convertToLens(vis, timeFilter) : null), - }; - }, updateVisTypeOnParamsChange: getVisTypeFromParams, visConfig: { defaults: { diff --git a/src/plugins/vis_types/xy/public/vis_types/index.ts b/src/plugins/vis_types/xy/public/vis_types/index.ts index 93c973b5316c9..2f7a03b6aaf1c 100644 --- a/src/plugins/vis_types/xy/public/vis_types/index.ts +++ b/src/plugins/vis_types/xy/public/vis_types/index.ts @@ -6,14 +6,27 @@ * Side Public License, v 1. */ +import type { VisTypeDefinition } from '@kbn/visualizations-plugin/public'; +import type { VisParams } from '../types'; import { areaVisTypeDefinition } from './area'; import { lineVisTypeDefinition } from './line'; import { histogramVisTypeDefinition } from './histogram'; import { horizontalBarVisTypeDefinition } from './horizontal_bar'; +import { convertToLens } from '../convert_to_lens'; export const visTypesDefinitions = [ areaVisTypeDefinition, lineVisTypeDefinition, histogramVisTypeDefinition, horizontalBarVisTypeDefinition, -]; +].map>((defenition) => { + return { + ...defenition, + 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/xy/public/vis_types/line.ts b/src/plugins/vis_types/xy/public/vis_types/line.ts index ca5e4172c0ac1..e0c7e081573f3 100644 --- a/src/plugins/vis_types/xy/public/vis_types/line.ts +++ b/src/plugins/vis_types/xy/public/vis_types/line.ts @@ -12,7 +12,7 @@ import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { Position, Fit } from '@elastic/charts'; import { AggGroupNames } from '@kbn/data-plugin/public'; -import { VisTypeDefinition, VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; +import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { defaultCountLabel, LabelRotation } from '@kbn/charts-plugin/public'; import { @@ -22,15 +22,13 @@ import { AxisMode, ThresholdLineStyle, InterpolationMode, - VisParams, } from '../types'; import { toExpressionAst } from '../to_ast'; import { ChartType } from '../../common'; import { optionTabs } from '../editor/common_config'; import { getVisTypeFromParams } from './get_vis_type_from_params'; -import { convertToLens } from '../convert_to_lens'; -export const lineVisTypeDefinition: VisTypeDefinition = { +export const lineVisTypeDefinition = { name: 'line', title: i18n.translate('visTypeXy.line.lineTitle', { defaultMessage: 'Line' }), icon: 'visLine', @@ -40,12 +38,6 @@ export const lineVisTypeDefinition: VisTypeDefinition = { fetchDatatable: true, toExpressionAst, getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], - navigateToLens: async (vis, timefilter) => (vis ? convertToLens(vis, timefilter) : null), - getExpressionVariables: async (vis, timeFilter) => { - return { - canNavigateToLens: Boolean(vis?.params ? await convertToLens(vis, timeFilter) : null), - }; - }, updateVisTypeOnParamsChange: getVisTypeFromParams, visConfig: { defaults: { From 03634151206dc6eab9593140de4f87a438e0b2e9 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Mon, 10 Oct 2022 16:41:08 +0300 Subject: [PATCH 08/21] updated limits --- packages/kbn-optimizer/limits.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 67064af8cddc5..0ecae8d688b71 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -140,6 +140,6 @@ pageLoadAssetSize: visTypeTimeseries: 55203 visTypeVega: 153573 visTypeVislib: 242838 - visTypeXy: 30000 + visTypeXy: 31000 visualizations: 90000 watcher: 43598 From e5373d9ef1be9c16aa7c7669c61531a32b425882 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Mon, 10 Oct 2022 17:04:10 +0300 Subject: [PATCH 09/21] Updated limits --- packages/kbn-optimizer/limits.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 0ecae8d688b71..7c445586f5ef2 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -140,6 +140,6 @@ pageLoadAssetSize: visTypeTimeseries: 55203 visTypeVega: 153573 visTypeVislib: 242838 - visTypeXy: 31000 + visTypeXy: 31200 visualizations: 90000 watcher: 43598 From 417b984700ab581dfe126951bcca0ead7d8c8b8c Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Mon, 10 Oct 2022 18:48:11 +0300 Subject: [PATCH 10/21] Added tests for configuration --- .../configurations/index.test.ts | 155 +++++++++++++++ .../xy/public/convert_to_lens/index.test.ts | 188 ++++++++++++++++++ .../xy/public/convert_to_lens/index.ts | 10 +- .../xy/public/sample_vis.test.mocks.ts | 9 +- 4 files changed, 357 insertions(+), 5 deletions(-) create mode 100644 src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.test.ts create mode 100644 src/plugins/vis_types/xy/public/convert_to_lens/index.test.ts diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.test.ts b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.test.ts new file mode 100644 index 0000000000000..2627104994d9a --- /dev/null +++ b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.test.ts @@ -0,0 +1,155 @@ +/* + * 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 } from '@kbn/visualizations-plugin/common/convert_to_lens'; +import { getConfiguration } from '.'; +import { Layer } from '..'; +import { ChartType } from '../..'; +import { sampleAreaVis } from '../../sample_vis.test.mocks'; +import { ChartMode, InterpolationMode } from '../../types'; + +describe('getConfiguration', () => { + const layers: Layer[] = [ + { + indexPatternId: '', + layerId: 'layer-1', + columns: [ + { columnId: '1', isBucketed: false }, + { columnId: '2', isBucketed: true, isSplit: false, operationType: 'date_histogram' }, + { columnId: '3', isBucketed: true, isSplit: true }, + ] as Column[], + columnOrder: [], + seriesId: '1', + collapseFn: 'max', + isReferenceLineLayer: false, + }, + { + indexPatternId: '', + layerId: 'layer-2', + columns: [ + { columnId: '4', isBucketed: false }, + { columnId: '5', isBucketed: true, isSplit: false, operationType: 'date_histogram' }, + ] as Column[], + columnOrder: [], + seriesId: '2', + collapseFn: undefined, + isReferenceLineLayer: false, + }, + { + indexPatternId: '', + layerId: 'layer-3', + columns: [{ columnId: '7', isBucketed: false }] as Column[], + columnOrder: [], + seriesId: '', + collapseFn: undefined, + isReferenceLineLayer: true, + }, + ]; + const series = [ + { + show: true, + type: ChartType.Area, + mode: ChartMode.Stacked, + data: { + label: 'Sum of total_quantity', + id: '1', + }, + drawLinesBetweenPoints: true, + showCircles: true, + circlesRadius: 5, + interpolate: InterpolationMode.Linear, + valueAxis: 'ValueAxis-1', + }, + { + show: true, + type: ChartType.Line, + mode: ChartMode.Stacked, + data: { + label: 'Sum of total_quantity 1', + id: '2', + }, + drawLinesBetweenPoints: true, + showCircles: true, + circlesRadius: 5, + interpolate: InterpolationMode.Linear, + valueAxis: 'ValueAxis-1', + }, + ]; + + test('should return correct configuration', () => { + expect(getConfiguration(layers, series, sampleAreaVis as any)).toEqual({ + axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + curveType: 'LINEAR', + fillOpacity: 0.5, + fittingFunction: undefined, + gridlinesVisibilitySettings: { x: false, yLeft: false, yRight: true }, + labelsOrientation: { x: -0, yLeft: -0, yRight: -90 }, + layers: [ + { + accessors: ['1'], + collapseFn: 'max', + isHistogram: true, + layerId: 'layer-1', + layerType: 'data', + palette: { name: 'default' }, + seriesType: 'area_stacked', + simpleView: false, + splitAccessor: '3', + xAccessor: '2', + xScaleType: 'ordinal', + yConfig: [{ axisMode: 'left', forAccessor: '1' }], + }, + { + accessors: ['4'], + collapseFn: undefined, + isHistogram: true, + layerId: 'layer-2', + layerType: 'data', + palette: { name: 'default' }, + seriesType: 'area_stacked', + simpleView: false, + splitAccessor: undefined, + xAccessor: '5', + xScaleType: 'ordinal', + yConfig: [{ axisMode: 'left', forAccessor: '4' }], + }, + { + accessors: ['7'], + layerId: 'layer-3', + layerType: 'referenceLine', + yConfig: [ + { + axisMode: 'left', + color: '#E7664C', + forAccessor: '7', + lineStyle: 'solid', + lineWidth: 1, + }, + ], + }, + ], + legend: { + isVisible: true, + legendSize: 'small', + maxLines: 1, + position: 'top', + shouldTruncate: true, + showSingleSeries: true, + }, + tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, + valueLabels: 'hide', + xTitle: undefined, + yLeftExtent: { enforce: true, lowerBound: undefined, mode: 'full', upperBound: undefined }, + yLeftScale: 'linear', + yRightExtent: undefined, + yRightScale: 'linear', + yRightTitle: undefined, + yTitle: 'Sum of total_quantity', + }); + }); +}); diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/xy/public/convert_to_lens/index.test.ts new file mode 100644 index 0000000000000..281771908a40d --- /dev/null +++ b/src/plugins/vis_types/xy/public/convert_to_lens/index.test.ts @@ -0,0 +1,188 @@ +/* + * 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 { convertToLens } from '.'; +import { sampleAreaVis } from '../sample_vis.test.mocks'; + +const mockGetColumnsFromVis = jest.fn(); +const mockCreateStaticValueColumn = jest.fn().mockReturnValue({ operationType: 'static_value' }); +const mockGetVisSchemas = jest.fn().mockReturnValue({ + metric: [{ aggId: '1' }], +}); +const mockGetConfiguration = jest.fn().mockReturnValue({}); + +jest.mock('../services', () => ({ + getDataViewsStart: jest.fn(() => ({ get: () => ({}), getDefault: () => ({}) })), +})); + +jest.mock('../utils/get_series_params', () => ({ + getSeriesParams: jest.fn(() => undefined), +})); + +jest.mock('@kbn/visualizations-plugin/public', () => ({ + convertToLensModule: Promise.resolve({ + getColumnsFromVis: jest.fn(() => mockGetColumnsFromVis()), + createStaticValueColumn: jest.fn(() => mockCreateStaticValueColumn()), + }), + getDataViewByIndexPatternId: jest.fn(() => ({ id: 'index-pattern' })), + getVisSchemas: jest.fn(() => mockGetVisSchemas()), +})); + +jest.mock('./configurations', () => ({ + getConfiguration: jest.fn(() => mockGetConfiguration()), +})); + +describe('convertToLens', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should return null if getColumnsFromVis returns null', async () => { + mockGetColumnsFromVis.mockReturnValue(null); + const result = await convertToLens(sampleAreaVis as any, { getAbsoluteTime: () => {} } as any); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should return null if sibling pipeline agg defined together with split series', async () => { + mockGetColumnsFromVis.mockReturnValue([ + { + buckets: { all: ['1'], customBuckets: { metric1: '2' } }, + }, + ]); + mockGetVisSchemas.mockReturnValue({ + metric: [{ aggId: '1' }], + group: [{}], + }); + const result = await convertToLens(sampleAreaVis as any, { getAbsoluteTime: () => {} } as any); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should return null if defined several metrics with terms split series which uses one of the metrics as order agg', async () => { + mockGetColumnsFromVis.mockReturnValue([ + { + buckets: { all: ['1'], customBuckets: { metric1: '2' } }, + columns: [{ isSplit: true, params: { orderBy: { type: 'column' } } }], + }, + ]); + mockGetVisSchemas.mockReturnValue({ + metric: [{ aggId: '1' }, { aggId: '2' }], + }); + const result = await convertToLens(sampleAreaVis as any, { getAbsoluteTime: () => {} } as any); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should return null if more than one axis left/right/top/bottom defined', async () => { + mockGetColumnsFromVis.mockReturnValue([ + { + buckets: { all: ['1'], customBuckets: {} }, + columns: [], + }, + ]); + mockGetVisSchemas.mockReturnValue({ + metric: [{ aggId: '1' }, { aggId: '2' }], + }); + const result = await convertToLens( + { + ...sampleAreaVis, + params: { + ...sampleAreaVis.params, + valueAxes: [ + ...sampleAreaVis.params.valueAxes, + { + id: 'ValueAxis-2', + name: 'LeftAxis-2', + type: 'value', + position: 'left', + data: { + id: '2', + }, + }, + ], + seriesParams: [ + ...sampleAreaVis.params.seriesParams, + { show: true, valueAxis: 'ValueAxis-2', data: { id: '2' } }, + ], + }, + } as any, + { getAbsoluteTime: () => {} } as any + ); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should state for valid vis', async () => { + mockGetColumnsFromVis.mockReturnValue([ + { + buckets: { all: ['2', '3'], customBuckets: { 1: '3' } }, + columns: [ + { columnId: '2', isBucketed: true }, + { columnId: '1', meta: { aggId: '1' } }, + { columnId: '3', isBucketed: true }, + ], + bucketCollapseFn: { sum: ['3'] }, + metrics: ['1'], + }, + { + buckets: { all: ['2'], customBuckets: {} }, + columns: [ + { columnId: '2', isBucketed: true }, + { columnId: '1', meta: { aggId: '2' } }, + ], + metrics: ['1'], + bucketCollapseFn: {}, + }, + ]); + mockGetVisSchemas.mockReturnValue({ + metric: [{ aggId: '1' }], + }); + const result = await convertToLens( + { + ...sampleAreaVis, + params: { + ...sampleAreaVis.params, + valueAxes: [ + ...sampleAreaVis.params.valueAxes, + { + id: 'ValueAxis-2', + name: 'LeftAxis-2', + type: 'value', + position: 'left', + data: { + id: '2', + }, + }, + ], + seriesParams: [ + ...sampleAreaVis.params.seriesParams, + { show: true, valueAxis: 'ValueAxis-2', data: { id: '2' } }, + ], + thresholdLine: { ...sampleAreaVis.params.thresholdLine, show: true }, + }, + } as any, + { getAbsoluteTime: () => {} } as any + ); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(mockGetConfiguration).toBeCalledTimes(1); + expect(mockCreateStaticValueColumn).toBeCalledTimes(1); + expect(result?.type).toEqual('lnsXY'); + expect(result?.layers.length).toEqual(3); + expect(result?.layers[0].columns).toEqual([ + { columnId: '2', isBucketed: true }, + { columnId: '1' }, + { columnId: '3', isBucketed: true }, + ]); + expect(result?.layers[1].columns).toEqual([ + { columnId: '2', isBucketed: true }, + { columnId: '1' }, + ]); + expect(result?.layers[2].columns).toEqual([{ operationType: 'static_value' }]); + }); +}); diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts index 46c23b6b97e43..7f4ad83dc18ce 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts @@ -135,11 +135,13 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte const layers = dataLayers.reduce((accLayers, l) => { const layerId = uuid.default(); const series = visibleSeries.find((s) => - l.columns.some((c) => c.meta.aggId.split('.')[0] === s.data.id) - ); - const collapseFn = Object.keys(l.bucketCollapseFn).find((key) => - l.bucketCollapseFn[key].includes(l.buckets.customBuckets[l.metrics[0]]) + l.columns.some((c) => !c.isBucketed && c.meta.aggId.split('.')[0] === s.data.id) ); + const collapseFn = l.bucketCollapseFn + ? Object.keys(l.bucketCollapseFn).find((key) => + l.bucketCollapseFn[key].includes(l.buckets.customBuckets[l.metrics[0]]) + ) + : undefined; accLayers.push({ indexPatternId, layerId, diff --git a/src/plugins/vis_types/xy/public/sample_vis.test.mocks.ts b/src/plugins/vis_types/xy/public/sample_vis.test.mocks.ts index e55debd7c77ba..b2660b7c66551 100644 --- a/src/plugins/vis_types/xy/public/sample_vis.test.mocks.ts +++ b/src/plugins/vis_types/xy/public/sample_vis.test.mocks.ts @@ -8,6 +8,8 @@ import { LegendSize } from '@kbn/visualizations-plugin/common'; +const mockUiStateGet = jest.fn().mockReturnValue(() => false); + export const sampleAreaVis = { type: { name: 'area', @@ -1918,5 +1920,10 @@ export const sampleAreaVis = { }, }, isHierarchical: () => false, - uiState: {}, + uiState: { + vis: { + legendOpen: false, + }, + get: mockUiStateGet, + }, }; From bbdb2ad982a9a11c6d37848143b1b4937ec118ab Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 11 Oct 2022 10:33:49 +0300 Subject: [PATCH 11/21] updated bundle size limit --- packages/kbn-optimizer/limits.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 7c445586f5ef2..a93acb29096a0 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -140,6 +140,6 @@ pageLoadAssetSize: visTypeTimeseries: 55203 visTypeVega: 153573 visTypeVislib: 242838 - visTypeXy: 31200 + visTypeXy: 31300 visualizations: 90000 watcher: 43598 From 23ef14761fde958ce42f779397ed5f8b73c63b14 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 11 Oct 2022 12:28:15 +0300 Subject: [PATCH 12/21] Added tests for getCustomBucketColumns --- .../public/convert_to_lens/utils.test.ts | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) 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 e978d057b9f4c..50f667430a8cb 100644 --- a/src/plugins/visualizations/public/convert_to_lens/utils.test.ts +++ b/src/plugins/visualizations/public/convert_to_lens/utils.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { BUCKET_TYPES, METRIC_TYPES } from '@kbn/data-plugin/common'; +import { BUCKET_TYPES, IAggConfig, METRIC_TYPES } from '@kbn/data-plugin/common'; import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { AggBasedColumn, @@ -27,6 +27,7 @@ import { getBucketColumns, getColumnIds, getColumnsWithoutReferenced, + getCustomBucketColumns, getMetricsWithoutDuplicates, isReferenced, isValidVis, @@ -648,4 +649,77 @@ describe('getColumnIds', () => { colId4, ]); }); + + describe('getCustomBucketColumns', () => { + const dataView = stubLogstashDataView; + const baseMetric = { + accessor: 0, + label: '', + format: { + id: undefined, + params: undefined, + }, + params: {}, + }; + const metric1: SchemaConfig = { + ...baseMetric, + accessor: 2, + aggType: METRIC_TYPES.COUNT, + aggId: '3', + }; + const metric2: SchemaConfig = { + ...baseMetric, + accessor: 3, + aggType: METRIC_TYPES.MAX, + aggId: '4', + }; + const customBucketsWithMetricIds = [ + { + customBucket: {} as IAggConfig, + metricIds: ['3', '4'], + }, + { + customBucket: {} as IAggConfig, + metricIds: ['5'], + }, + ]; + test('return custom buckets columns and map', () => { + mockConvertBucketToColumns.mockReturnValueOnce({ + columnId: 'col-1', + operationType: 'date_histogram', + }); + mockConvertBucketToColumns.mockReturnValueOnce({ + columnId: 'col-2', + operationType: 'terms', + }); + expect( + getCustomBucketColumns( + customBucketsWithMetricIds, + [ + { columnId: 'col-3', meta: { aggId: '3' } }, + { columnId: 'col-4', meta: { aggId: '4' } }, + { columnId: 'col-5', meta: { aggId: '5' } }, + ] as AggBasedColumn[], + dataView, + [metric1, metric2] + ) + ).toEqual({ + customBucketColumns: [ + { + columnId: 'col-1', + operationType: 'date_histogram', + }, + { + columnId: 'col-2', + operationType: 'terms', + }, + ], + customBucketsMap: { + 'col-3': 'col-1', + 'col-4': 'col-1', + 'col-5': 'col-2', + }, + }); + }); + }); }); From 1fa30fecc115bcc50761b4820b6b344319483cc4 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 11 Oct 2022 14:39:39 +0300 Subject: [PATCH 13/21] Added some functional tests --- .../group3/open_in_lens/agg_based/index.ts | 1 + .../lens/group3/open_in_lens/agg_based/xy.ts | 73 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/xy.ts diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/index.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/index.ts index 66de13c67d94c..20595f9e97b11 100644 --- a/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/index.ts +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/index.ts @@ -11,5 +11,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('Agg based Vis to Lens', function () { loadTestFile(require.resolve('./pie')); loadTestFile(require.resolve('./metric')); + loadTestFile(require.resolve('./xy')); }); } diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/xy.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/xy.ts new file mode 100644 index 0000000000000..161a3549e856c --- /dev/null +++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/agg_based/xy.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const { visualize, visEditor, lens, timePicker, header, visChart } = getPageObjects([ + 'visualize', + 'lens', + 'visEditor', + 'timePicker', + 'header', + 'visChart', + ]); + + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + + describe('XY', function describeIndexTests() { + const isNewChartsLibraryEnabled = true; + + before(async () => { + await visualize.initTests(isNewChartsLibraryEnabled); + }); + + beforeEach(async () => { + await visualize.navigateToNewAggBasedVisualization(); + await visualize.clickLineChart(); + await visualize.clickNewSearch(); + await timePicker.setDefaultAbsoluteRange(); + }); + + it('should show the "Edit Visualization in Lens" menu item', async () => { + const button = await testSubjects.exists('visualizeEditInLensButton'); + expect(button).to.eql(true); + }); + + it('should hide the "Edit Visualization in Lens" menu item if dot size aggregation is defined', async () => { + await visEditor.clickBucket('Dot size', 'metrics'); + await visEditor.selectAggregation('Max', 'metrics'); + await visEditor.selectField('memory', 'metrics'); + await visEditor.clickGo(isNewChartsLibraryEnabled); + const button = await testSubjects.exists('visualizeEditInLensButton'); + expect(button).to.eql(false); + }); + + it('should convert to Lens', async () => { + await visEditor.clickBucket('Split series'); + await visEditor.selectAggregation('Terms'); + await visEditor.selectField('machine.os.raw'); + await header.waitUntilLoadingHasFinished(); + await visEditor.clickGo(isNewChartsLibraryEnabled); + const expectedData = await visChart.getLegendEntriesXYCharts('xyVisChart'); + + const button = await testSubjects.find('visualizeEditInLensButton'); + await button.click(); + await lens.waitForVisualization('xyVisChart'); + const data = await lens.getCurrentChartDebugState('xyVisChart'); + await retry.try(async () => { + const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); + expect(dimensions).to.have.length(2); + expect(await dimensions[0].getVisibleText()).to.be('Count'); + expect(await dimensions[1].getVisibleText()).to.be('machine.os.raw: Descending'); + }); + expect(data?.legend?.items.map((item) => item.name)).to.eql(expectedData); + }); + }); +} From 67027538439a78d801b2873332a86936e5f420cf Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 11 Oct 2022 15:37:42 +0300 Subject: [PATCH 14/21] Fix gauge/goal configurations --- .../configurations/goal.test.ts | 4 +- .../convert_to_lens/configurations/goal.ts | 16 ++++-- .../public/convert_to_lens/gauge.test.ts | 44 ++++++++------- .../gauge/public/convert_to_lens/gauge.ts | 8 +-- .../gauge/public/convert_to_lens/goal.test.ts | 54 +++++++++++-------- .../gauge/public/convert_to_lens/goal.ts | 8 +-- .../convert_to_lens/lib/metrics/index.ts | 1 - .../lib/metrics/static_value.ts | 23 -------- 8 files changed, 80 insertions(+), 78 deletions(-) delete mode 100644 src/plugins/visualizations/common/convert_to_lens/lib/metrics/static_value.ts diff --git a/src/plugins/vis_types/gauge/public/convert_to_lens/configurations/goal.test.ts b/src/plugins/vis_types/gauge/public/convert_to_lens/configurations/goal.test.ts index 7c04f19400272..6f472ca29af46 100644 --- a/src/plugins/vis_types/gauge/public/convert_to_lens/configurations/goal.test.ts +++ b/src/plugins/vis_types/gauge/public/convert_to_lens/configurations/goal.test.ts @@ -60,7 +60,7 @@ describe('getConfiguration', () => { const metricAccessor = 'metric-id'; const breakdownByAccessor = 'bucket-id'; const metrics = [metricAccessor]; - const buckets = [breakdownByAccessor]; + const buckets = { all: [breakdownByAccessor], customBuckets: {} }; const maxAccessor = 'max-accessor-id'; const collapseFn = 'sum'; expect( @@ -69,7 +69,7 @@ describe('getConfiguration', () => { buckets, maxAccessor, columnsWithoutReferenced: [], - bucketCollapseFn: { [metricAccessor]: collapseFn }, + bucketCollapseFn: { [collapseFn]: [breakdownByAccessor] }, }) ).toEqual({ breakdownByAccessor, diff --git a/src/plugins/vis_types/gauge/public/convert_to_lens/configurations/goal.ts b/src/plugins/vis_types/gauge/public/convert_to_lens/configurations/goal.ts index 91e58333e8d56..ec56280fa25cc 100644 --- a/src/plugins/vis_types/gauge/public/convert_to_lens/configurations/goal.ts +++ b/src/plugins/vis_types/gauge/public/convert_to_lens/configurations/goal.ts @@ -22,14 +22,22 @@ export const getConfiguration = ( bucketCollapseFn, }: { metrics: string[]; - buckets: string[]; + buckets: { + all: string[]; + customBuckets: Record; + }; maxAccessor: string; columnsWithoutReferenced: Column[]; - bucketCollapseFn?: Record; + bucketCollapseFn?: Record; } ): MetricVisConfiguration => { const [metricAccessor] = metrics; - const [breakdownByAccessor] = buckets; + const [breakdownByAccessor] = buckets.all; + const collapseFn = bucketCollapseFn + ? Object.keys(bucketCollapseFn).find((key) => + bucketCollapseFn[key].includes(breakdownByAccessor) + ) + : undefined; return { layerId, layerType: 'data', @@ -37,7 +45,7 @@ export const getConfiguration = ( metricAccessor, breakdownByAccessor, maxAccessor, - collapseFn: Object.values(bucketCollapseFn ?? {})[0], + collapseFn, subtitle: gauge.labels.show && gauge.style.subText ? gauge.style.subText : undefined, }; }; diff --git a/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.test.ts b/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.test.ts index f7c07dafd85c6..0da750bfdcef0 100644 --- a/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.test.ts +++ b/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.test.ts @@ -104,22 +104,26 @@ describe('convertToLens', () => { }); test('should return null if metrics count is more than 1', async () => { - mockGetColumnsFromVis.mockReturnValue({ - metrics: ['1', '2'], - buckets: [], - columns: [{ columnId: '2' }, { columnId: '1' }], - }); + 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 null if metric column data type is different from number', async () => { - mockGetColumnsFromVis.mockReturnValue({ - metrics: ['1'], - buckets: [], - columns: [{ columnId: '2' }, { columnId: '1', dataType: 'string' }], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1'], + buckets: { all: [] }, + columns: [{ columnId: '2' }, { columnId: '1', dataType: 'string' }], + }, + ]); const result = await convertToLens(vis, timefilter); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(result).toBeNull(); @@ -129,15 +133,17 @@ describe('convertToLens', () => { layerType: 'data', }; - mockGetColumnsFromVis.mockReturnValue({ - metrics: ['1'], - buckets: [], - columns: [{ columnId: '1', dataType: 'number' }], - columnsWithoutReferenced: [ - { columnId: '1', meta: { aggId: 'agg-1' } }, - { columnId: '2', meta: { aggId: 'agg-2' } }, - ], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1'], + buckets: { all: [] }, + 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); diff --git a/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.ts b/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.ts index be664d3500a7c..d9de4df2c5f0b 100644 --- a/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.ts +++ b/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.ts @@ -53,7 +53,7 @@ export const convertToLens: ConvertGaugeVisToLensVisualization = async (vis, tim const percentageModeConfig = getPercentageModeConfig(vis.params.gauge, false); - const result = getColumnsFromVis( + const layers = getColumnsFromVis( vis, timefilter, dataView, @@ -63,12 +63,14 @@ export const convertToLens: ConvertGaugeVisToLensVisualization = async (vis, tim { dropEmptyRowsInDateHistogram: true, ...percentageModeConfig } ); - if (result === null) { + if (layers === null) { return null; } + const result = layers[0]; + // for now, multiple metrics are not supported - if (result.metrics.length > 1 || result.buckets.length) { + if (result.metrics.length > 1 || result.buckets.all.length) { return null; } diff --git a/src/plugins/vis_types/gauge/public/convert_to_lens/goal.test.ts b/src/plugins/vis_types/gauge/public/convert_to_lens/goal.test.ts index 4d9247293e7a8..88566694f55bf 100644 --- a/src/plugins/vis_types/gauge/public/convert_to_lens/goal.test.ts +++ b/src/plugins/vis_types/gauge/public/convert_to_lens/goal.test.ts @@ -104,31 +104,37 @@ describe('convertToLens', () => { }); test('should return null if metrics count is more than 1', async () => { - mockGetColumnsFromVis.mockReturnValue({ - metrics: ['1', '2'], - columns: [{ columnId: '2' }, { columnId: '1' }], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1', '2'], + columns: [{ columnId: '2' }, { columnId: '1' }], + }, + ]); const result = await convertToLens(vis, timefilter); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(result).toBeNull(); }); test('should return null if buckets count is more than 1', async () => { - mockGetColumnsFromVis.mockReturnValue({ - metrics: [], - buckets: ['1', '2'], - columns: [{ columnId: '2' }, { columnId: '1' }], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: [], + buckets: { all: ['1', '2'] }, + columns: [{ columnId: '2' }, { columnId: '1' }], + }, + ]); const result = await convertToLens(vis, timefilter); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(result).toBeNull(); }); test('should return null if metric column data type is different from number', async () => { - mockGetColumnsFromVis.mockReturnValue({ - metrics: ['1'], - buckets: ['2'], - columns: [{ columnId: '2' }, { columnId: '1', dataType: 'string' }], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1'], + buckets: { all: ['2'] }, + columns: [{ columnId: '2' }, { columnId: '1', dataType: 'string' }], + }, + ]); const result = await convertToLens(vis, timefilter); expect(mockGetColumnsFromVis).toBeCalledTimes(1); expect(result).toBeNull(); @@ -139,15 +145,17 @@ describe('convertToLens', () => { metricAccessor: '1', }; - mockGetColumnsFromVis.mockReturnValue({ - metrics: ['1'], - buckets: ['2'], - columns: [{ columnId: '2' }, { columnId: '1', dataType: 'number' }], - columnsWithoutReferenced: [ - { columnId: '1', meta: { aggId: 'agg-1' } }, - { columnId: '2', meta: { aggId: 'agg-2' } }, - ], - }); + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1'], + buckets: { all: ['2'] }, + columns: [{ columnId: '2' }, { 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); diff --git a/src/plugins/vis_types/gauge/public/convert_to_lens/goal.ts b/src/plugins/vis_types/gauge/public/convert_to_lens/goal.ts index a57dfedb02581..0b13d1891ce19 100644 --- a/src/plugins/vis_types/gauge/public/convert_to_lens/goal.ts +++ b/src/plugins/vis_types/gauge/public/convert_to_lens/goal.ts @@ -53,7 +53,7 @@ export const convertToLens: ConvertGoalVisToLensVisualization = async (vis, time const percentageModeConfig = getPercentageModeConfig(vis.params.gauge, false); - const result = getColumnsFromVis( + const layers = getColumnsFromVis( vis, timefilter, dataView, @@ -63,12 +63,14 @@ export const convertToLens: ConvertGoalVisToLensVisualization = async (vis, time { dropEmptyRowsInDateHistogram: true, ...percentageModeConfig } ); - if (result === null) { + if (layers === null) { return null; } + const result = layers[0]; + // for now, multiple metrics are not supported - if (result.metrics.length > 1 || result.buckets.length > 1) { + if (result.metrics.length > 1 || result.buckets.all.length > 1) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/index.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/index.ts index e912ef80045be..c34e328332339 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/index.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/index.ts @@ -9,4 +9,3 @@ export { getFormulaForPipelineAgg } from './formula'; export { convertMetricToColumns } from './metrics'; export { getPercentageColumnFormulaColumn } from './percentage_formula'; -export { createStaticValueColumn } from './static_value'; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/static_value.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/static_value.ts deleted file mode 100644 index 8a763aa80c678..0000000000000 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/static_value.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import uuid from 'uuid'; -import { StaticValueColumn } from '../../types'; - -export const createStaticValueColumn = (staticValue: number): StaticValueColumn => ({ - columnId: uuid(), - operationType: 'static_value', - references: [], - dataType: 'number', - isStaticValue: true, - isBucketed: false, - isSplit: false, - params: { - value: staticValue.toString(), - }, -}); From 2ff0106ae7ecdaf7c251eb45ed7f3fc4c4d150ad Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Wed, 12 Oct 2022 17:35:06 +0300 Subject: [PATCH 15/21] Fixed nits --- .../vis_types/gauge/public/convert_to_lens/gauge.ts | 12 ++++++------ .../vis_types/gauge/public/convert_to_lens/goal.ts | 12 ++++++------ .../vis_types/metric/public/convert_to_lens/index.ts | 12 ++++++------ .../vis_types/pie/public/convert_to_lens/index.ts | 8 ++++---- .../vis_types/table/public/convert_to_lens/index.ts | 12 ++++++------ .../vis_types/xy/public/convert_to_lens/index.ts | 9 ++++----- .../visualizations/public/convert_to_lens/schemas.ts | 3 +-- 7 files changed, 33 insertions(+), 35 deletions(-) diff --git a/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.ts b/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.ts index d9de4df2c5f0b..080d5e84561a9 100644 --- a/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.ts +++ b/src/plugins/vis_types/gauge/public/convert_to_lens/gauge.ts @@ -67,15 +67,15 @@ export const convertToLens: ConvertGaugeVisToLensVisualization = async (vis, tim return null; } - const result = layers[0]; + const [layerConfig] = layers; // for now, multiple metrics are not supported - if (result.metrics.length > 1 || result.buckets.all.length) { + if (layerConfig.metrics.length > 1 || layerConfig.buckets.all.length) { return null; } - if (result.metrics[0]) { - const metric = result.columns.find(({ columnId }) => columnId === result.metrics[0]); + if (layerConfig.metrics[0]) { + const metric = layerConfig.columns.find(({ columnId }) => columnId === layerConfig.metrics[0]); if (metric?.dataType !== 'number') { return null; } @@ -84,11 +84,11 @@ export const convertToLens: ConvertGaugeVisToLensVisualization = async (vis, tim const layerId = uuid(); const indexPatternId = dataView.id!; - const metricAccessor = result.metrics[0]; + const metricAccessor = layerConfig.metrics[0]; const { min, max, isPercentageMode } = percentageModeConfig as PercentageModeConfigWithMinMax; const minColumn = createStaticValueColumn(isPercentageMode ? 0 : min); const maxColumn = createStaticValueColumn(isPercentageMode ? 1 : max); - const columns = [...result.columns, minColumn, maxColumn]; + const columns = [...layerConfig.columns, minColumn, maxColumn]; return { type: 'lnsGauge', diff --git a/src/plugins/vis_types/gauge/public/convert_to_lens/goal.ts b/src/plugins/vis_types/gauge/public/convert_to_lens/goal.ts index 0b13d1891ce19..624ce45b3e848 100644 --- a/src/plugins/vis_types/gauge/public/convert_to_lens/goal.ts +++ b/src/plugins/vis_types/gauge/public/convert_to_lens/goal.ts @@ -67,15 +67,15 @@ export const convertToLens: ConvertGoalVisToLensVisualization = async (vis, time return null; } - const result = layers[0]; + const [layerConfig] = layers; // for now, multiple metrics are not supported - if (result.metrics.length > 1 || result.buckets.all.length > 1) { + if (layerConfig.metrics.length > 1 || layerConfig.buckets.all.length > 1) { return null; } - if (result.metrics[0]) { - const metric = result.columns.find(({ columnId }) => columnId === result.metrics[0]); + if (layerConfig.metrics[0]) { + const metric = layerConfig.columns.find(({ columnId }) => columnId === layerConfig.metrics[0]); if (metric?.dataType !== 'number') { return null; } @@ -83,7 +83,7 @@ export const convertToLens: ConvertGoalVisToLensVisualization = async (vis, time const { isPercentageMode, max } = percentageModeConfig as PercentageModeConfigWithMinMax; const maxColumn = createStaticValueColumn(isPercentageMode ? 1 : max); - const columns = [...result.columns, maxColumn]; + const columns = [...layerConfig.columns, maxColumn]; const layerId = uuid(); const indexPatternId = dataView.id!; @@ -102,7 +102,7 @@ export const convertToLens: ConvertGoalVisToLensVisualization = async (vis, time vis.params, getPalette(vis.params.gauge, percentageModeConfig, true), { - ...result, + ...layerConfig, maxAccessor: maxColumn.columnId, } ), diff --git a/src/plugins/vis_types/metric/public/convert_to_lens/index.ts b/src/plugins/vis_types/metric/public/convert_to_lens/index.ts index bbdd791f33e8d..5b9cb985a2799 100644 --- a/src/plugins/vis_types/metric/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/metric/public/convert_to_lens/index.ts @@ -60,15 +60,15 @@ export const convertToLens: ConvertMetricVisToLensVisualization = async (vis, ti return null; } - const result = layers[0]; + const [layerConfig] = layers; // for now, multiple metrics are not supported - if (result.metrics.length > 1 || result.buckets.all.length > 1) { + if (layerConfig.metrics.length > 1 || layerConfig.buckets.all.length > 1) { return null; } - if (result.metrics[0]) { - const metric = result.columns.find(({ columnId }) => columnId === result.metrics[0]); + if (layerConfig.metrics[0]) { + const metric = layerConfig.columns.find(({ columnId }) => columnId === layerConfig.metrics[0]); if (metric?.dataType !== 'number') { return null; } @@ -83,7 +83,7 @@ export const convertToLens: ConvertMetricVisToLensVisualization = async (vis, ti { indexPatternId, layerId, - columns: result.columns.map(excludeMetaFromColumn), + columns: layerConfig.columns.map(excludeMetaFromColumn), columnOrder: [], }, ], @@ -91,7 +91,7 @@ export const convertToLens: ConvertMetricVisToLensVisualization = async (vis, ti layerId, vis.params, getPalette(vis.params.metric, percentageModeConfig), - result + layerConfig ), indexPatternIds: [indexPatternId], }; diff --git a/src/plugins/vis_types/pie/public/convert_to_lens/index.ts b/src/plugins/vis_types/pie/public/convert_to_lens/index.ts index ecfeee600d036..c7231af7098c8 100644 --- a/src/plugins/vis_types/pie/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/pie/public/convert_to_lens/index.ts @@ -54,11 +54,11 @@ export const convertToLens: ConvertPieToLensVisualization = async (vis, timefilt return null; } - const result = layers[0]; + const [layerConfig] = layers; // doesn't support more than three split slice levels // doesn't support pie without at least one split slice - if (result.buckets.all.length > 3 || !result.buckets.all.length) { + if (layerConfig.buckets.all.length > 3 || !layerConfig.buckets.all.length) { return null; } @@ -71,11 +71,11 @@ export const convertToLens: ConvertPieToLensVisualization = async (vis, timefilt { indexPatternId, layerId, - columns: result.columns.map(excludeMetaFromColumn), + columns: layerConfig.columns.map(excludeMetaFromColumn), columnOrder: [], }, ], - configuration: getConfiguration(layerId, vis, result), + configuration: getConfiguration(layerId, vis, layerConfig), indexPatternIds: [indexPatternId], }; }; 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 6ef3ab53250cf..e69faccbfd7ec 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 @@ -61,7 +61,7 @@ export const convertToLens: ConvertTableToLensVisualization = async (vis, timefi return null; } - const result = layers[0]; + const [layerConfig] = layers; if (vis.params.percentageCol) { const visSchemas = getVisSchemas(vis, { @@ -80,12 +80,12 @@ export const convertToLens: ConvertTableToLensVisualization = async (vis, timefi if (!percentageColumn) { return null; } - result.columns.splice( - result.columnsWithoutReferenced.findIndex((c) => c.meta.aggId === metricAgg.aggId) + 1, + layerConfig.columns.splice( + layerConfig.columnsWithoutReferenced.findIndex((c) => c.meta.aggId === metricAgg.aggId) + 1, 0, percentageColumn ); - result.columnsWithoutReferenced.push(percentageColumn); + layerConfig.columnsWithoutReferenced.push(percentageColumn); } const layerId = uuid(); @@ -96,11 +96,11 @@ export const convertToLens: ConvertTableToLensVisualization = async (vis, timefi { indexPatternId, layerId, - columns: result.columns.map(excludeMetaFromColumn), + columns: layerConfig.columns.map(excludeMetaFromColumn), columnOrder: [], }, ], - configuration: getConfiguration(layerId, vis.params, result), + configuration: getConfiguration(layerId, vis.params, layerConfig), indexPatternIds: [indexPatternId], }; }; diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts index 7f4ad83dc18ce..b8b41a5491c18 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts @@ -132,7 +132,7 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte const uuid = await import('uuid/v4'); - const layers = dataLayers.reduce((accLayers, l) => { + const layers = dataLayers.map((l) => { const layerId = uuid.default(); const series = visibleSeries.find((s) => l.columns.some((c) => !c.isBucketed && c.meta.aggId.split('.')[0] === s.data.id) @@ -142,7 +142,7 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte l.bucketCollapseFn[key].includes(l.buckets.customBuckets[l.metrics[0]]) ) : undefined; - accLayers.push({ + return { indexPatternId, layerId, columns: l.columns.map(excludeMetaFromColumn), @@ -150,9 +150,8 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte seriesId: series?.data.id!, collapseFn, isReferenceLineLayer: false, - }); - return accLayers; - }, []); + }; + }); if (vis.params.thresholdLine.show) { layers.push({ diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.ts index 884e8d5f42abb..3a225e540faae 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.ts @@ -173,8 +173,7 @@ export const getColumnsFromVis = ( const layers = []; if (series && series.length) { - for (let i = 0; i < series.length; i++) { - const metricAggIds = series[i].metrics; + for (const { metrics: metricAggIds } of series) { const metrics = aggs.filter( (agg) => agg.aggId && metricAggIds.includes(agg.aggId.split('.')[0]) ); From ad33cc624777e5f64ffa658cbcebb1d5721bd870 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 13 Oct 2022 12:09:11 +0300 Subject: [PATCH 16/21] Fixed parent pipeline aggregation, multi split series and respect Show values option --- .../configurations/index.test.ts | 4 +++ .../convert_to_lens/configurations/index.ts | 29 +++++-------------- .../xy/public/convert_to_lens/index.test.ts | 10 +++++++ .../xy/public/convert_to_lens/index.ts | 11 ++++++- src/plugins/vis_types/xy/public/to_ast.ts | 1 + 5 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.test.ts b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.test.ts index 2627104994d9a..a404fde4ea985 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.test.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.test.ts @@ -23,6 +23,7 @@ describe('getConfiguration', () => { { columnId: '2', isBucketed: true, isSplit: false, operationType: 'date_histogram' }, { columnId: '3', isBucketed: true, isSplit: true }, ] as Column[], + metrics: ['1'], columnOrder: [], seriesId: '1', collapseFn: 'max', @@ -35,6 +36,7 @@ describe('getConfiguration', () => { { columnId: '4', isBucketed: false }, { columnId: '5', isBucketed: true, isSplit: false, operationType: 'date_histogram' }, ] as Column[], + metrics: ['4'], columnOrder: [], seriesId: '2', collapseFn: undefined, @@ -45,6 +47,7 @@ describe('getConfiguration', () => { layerId: 'layer-3', columns: [{ columnId: '7', isBucketed: false }] as Column[], columnOrder: [], + metrics: ['7'], seriesId: '', collapseFn: undefined, isReferenceLineLayer: true, @@ -143,6 +146,7 @@ describe('getConfiguration', () => { }, tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, valueLabels: 'hide', + valuesInLegend: false, xTitle: undefined, yLeftExtent: { enforce: true, lowerBound: undefined, mode: 'full', upperBound: undefined }, yLeftScale: 'linear', diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts index f449d2d2ad6c8..2052fd067d7a2 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts @@ -113,12 +113,6 @@ function getDataLayers( ): XYDataLayerConfig[] { return layers.map((layer) => { const xColumn = layer.columns.find((c) => c.isBucketed && !c.isSplit); - const yAccessors = layer.columns.reduce((acc, column) => { - if (!column.isBucketed) { - acc.push(column.columnId); - } - return acc; - }, []); const splitAccessor = layer.columns.find( (column) => column.isBucketed && column.isSplit )?.columnId; @@ -134,15 +128,15 @@ function getDataLayers( const seriesType = getSeriesType(serie?.type, serie?.mode, isHorizontal, isPercentage); return { layerId: layer.layerId, - accessors: yAccessors, + accessors: layer.metrics, layerType: 'data', seriesType, xAccessor: xColumn?.columnId, simpleView: false, splitAccessor, palette: vis.params.palette ?? vis.type.visConfig.defaults.palette, - yConfig: yAccessors.map((accessor) => ({ - forAccessor: accessor, + yConfig: layer.metrics.map((metricId) => ({ + forAccessor: metricId, axisMode: getYAxisPosition(yAxis?.position ?? 'left'), })), xScaleType: getXScaleType(xColumn), @@ -153,15 +147,7 @@ function getDataLayers( } function getReferenceLineLayers( - layers: Array<{ - indexPatternId: string; - layerId: string; - columns: Column[]; - columnOrder: never[]; - seriesId: string; - isReferenceLineLayer: boolean; - collapseFn?: string; - }>, + layers: Layer[], vis: Vis ): XYReferenceLineLayerConfig[] { const thresholdLineConfig = vis.params.thresholdLine ?? vis.type.visConfig.defaults.thresholdLine; @@ -171,10 +157,10 @@ function getReferenceLineLayers( return { layerType: 'referenceLine', layerId: layer.layerId, - accessors: layer.columns.map((c) => c.columnId), - yConfig: layer.columns.map((c) => { + accessors: layer.metrics, + yConfig: layer.metrics.map((metricId) => { return { - forAccessor: c.columnId, + forAccessor: metricId, axisMode: getYAxisPosition(yAxis?.position ?? 'left'), color: thresholdLineConfig.color, lineWidth: thresholdLineConfig.width !== null ? thresholdLineConfig.width : undefined, @@ -261,6 +247,7 @@ export const getConfiguration = ( xTitle: xAxis.title.text, valueLabels: vis.params.labels.show ?? vis.type.visConfig.defaults.labels?.show ? 'show' : 'hide', + valuesInLegend: Boolean(vis.params.labels.show ?? vis.type.visConfig.defaults.labels?.show), curveType: getCurveType( series[0].interpolate === InterpolationMode.StepAfter ? InterpolationMode.Linear diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/xy/public/convert_to_lens/index.test.ts index 281771908a40d..f5f1adef6f629 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/index.test.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/index.test.ts @@ -49,6 +49,16 @@ describe('convertToLens', () => { expect(result).toBeNull(); }); + test('should return null if multi split series defined', async () => { + mockGetVisSchemas.mockReturnValue({ + metric: [{ aggId: '1' }], + group: [{}, {}], + }); + const result = await convertToLens(sampleAreaVis as any, { getAbsoluteTime: () => {} } as any); + expect(mockGetVisSchemas).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + test('should return null if sibling pipeline agg defined together with split series', async () => { mockGetColumnsFromVis.mockReturnValue([ { diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts index b8b41a5491c18..62130c1ecc704 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts @@ -20,6 +20,7 @@ export interface Layer { indexPatternId: string; layerId: string; columns: Column[]; + metrics: string[]; columnOrder: never[]; seriesId: string; isReferenceLineLayer: boolean; @@ -58,6 +59,11 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte timeRange: timefilter.getAbsoluteTime(), }); + // doesn't support multi split series + if (visSchemas.group && visSchemas.group.length > 1) { + return null; + } + const firstValueAxesId = vis.params.valueAxes[0].id; const updatedSeries = getSeriesParams( vis.data.aggs, @@ -146,6 +152,7 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte indexPatternId, layerId, columns: l.columns.map(excludeMetaFromColumn), + metrics: l.metrics, columnOrder: [], seriesId: series?.data.id!, collapseFn, @@ -154,11 +161,13 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte }); if (vis.params.thresholdLine.show) { + const staticValueColumn = createStaticValueColumn(vis.params.thresholdLine.value || 0); layers.push({ indexPatternId, layerId: uuid.default(), - columns: [createStaticValueColumn(vis.params.thresholdLine.value || 0)], + columns: [staticValueColumn], columnOrder: [], + metrics: [staticValueColumn.columnId], isReferenceLineLayer: true, collapseFn: undefined, seriesId: '', diff --git a/src/plugins/vis_types/xy/public/to_ast.ts b/src/plugins/vis_types/xy/public/to_ast.ts index bd11e92d47dca..b584fbac26bba 100644 --- a/src/plugins/vis_types/xy/public/to_ast.ts +++ b/src/plugins/vis_types/xy/public/to_ast.ts @@ -434,6 +434,7 @@ export const toExpressionAst: VisToExpressionAst = async (vis, params splitColumnAccessor: dimensions.splitColumn?.map(prepareVisDimension), splitRowAccessor: dimensions.splitRow?.map(prepareVisDimension), valueLabels: vis.params.labels.show ? 'show' : 'hide', + valuesInLegend: vis.params.labels.show, singleTable: true, }); From ca791a9679a3cea7a44761981e1622c8d33babd6 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 13 Oct 2022 13:37:37 +0300 Subject: [PATCH 17/21] Added support of current time marker, use one layer for metrics with the same chart type and mode --- .../configurations/index.test.ts | 7 ++-- .../convert_to_lens/configurations/index.ts | 36 +++++++++++++------ .../xy/public/convert_to_lens/index.ts | 32 ++++++++++++----- .../convert_to_lens/types/configurations.ts | 1 + 4 files changed, 55 insertions(+), 21 deletions(-) diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.test.ts b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.test.ts index a404fde4ea985..2d8c7da9ba801 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.test.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.test.ts @@ -25,7 +25,7 @@ describe('getConfiguration', () => { ] as Column[], metrics: ['1'], columnOrder: [], - seriesId: '1', + seriesIdsMap: { 1: '1' }, collapseFn: 'max', isReferenceLineLayer: false, }, @@ -38,7 +38,7 @@ describe('getConfiguration', () => { ] as Column[], metrics: ['4'], columnOrder: [], - seriesId: '2', + seriesIdsMap: { 4: '2' }, collapseFn: undefined, isReferenceLineLayer: false, }, @@ -48,7 +48,7 @@ describe('getConfiguration', () => { columns: [{ columnId: '7', isBucketed: false }] as Column[], columnOrder: [], metrics: ['7'], - seriesId: '', + seriesIdsMap: {}, collapseFn: undefined, isReferenceLineLayer: true, }, @@ -154,6 +154,7 @@ describe('getConfiguration', () => { yRightScale: 'linear', yRightTitle: undefined, yTitle: 'Sum of total_quantity', + showCurrentTimeMarker: false, }); }); }); diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts index 2052fd067d7a2..b3b62ea9ba7e1 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts @@ -116,16 +116,23 @@ function getDataLayers( const splitAccessor = layer.columns.find( (column) => column.isBucketed && column.isSplit )?.columnId; - const serie = series.find((s) => s.data.id === layer.seriesId); + // as type and mode will be the same for all metrics we can use first to define it + const firstSeries = series.find((s) => s.data.id === layer.seriesIdsMap[layer.metrics[0]]); const isHistogram = xColumn?.operationType === 'date_histogram' || (xColumn?.operationType === 'range' && xColumn.params.type === 'histogram'); - const yAxis = (vis.params.valueAxes ?? vis.type.visConfig.defaults.valueAxes).find( - (axis) => axis.id === serie?.valueAxis + const firstYAxis = (vis.params.valueAxes ?? vis.type.visConfig.defaults.valueAxes).find( + (axis) => axis.id === firstSeries?.valueAxis + ); + const isPercentage = firstYAxis?.scale.mode === 'percentage'; + const isHorizontal = + firstYAxis?.position !== Position.Left && firstYAxis?.position !== Position.Right; + const seriesType = getSeriesType( + firstSeries?.type, + firstSeries?.mode, + isHorizontal, + isPercentage ); - const isPercentage = yAxis?.scale.mode === 'percentage'; - const isHorizontal = yAxis?.position !== Position.Left && yAxis?.position !== Position.Right; - const seriesType = getSeriesType(serie?.type, serie?.mode, isHorizontal, isPercentage); return { layerId: layer.layerId, accessors: layer.metrics, @@ -135,10 +142,16 @@ function getDataLayers( simpleView: false, splitAccessor, palette: vis.params.palette ?? vis.type.visConfig.defaults.palette, - yConfig: layer.metrics.map((metricId) => ({ - forAccessor: metricId, - axisMode: getYAxisPosition(yAxis?.position ?? 'left'), - })), + yConfig: layer.metrics.map((metricId) => { + const serie = series.find((s) => s.data.id === layer.seriesIdsMap[metricId]); + const yAxis = (vis.params.valueAxes ?? vis.type.visConfig.defaults.valueAxes).find( + (axis) => axis.id === serie?.valueAxis + ); + return { + forAccessor: metricId, + axisMode: getYAxisPosition(yAxis?.position ?? 'left'), + }; + }), xScaleType: getXScaleType(xColumn), isHistogram, collapseFn: layer.collapseFn, @@ -248,6 +261,9 @@ export const getConfiguration = ( valueLabels: vis.params.labels.show ?? vis.type.visConfig.defaults.labels?.show ? 'show' : 'hide', valuesInLegend: Boolean(vis.params.labels.show ?? vis.type.visConfig.defaults.labels?.show), + showCurrentTimeMarker: isTimeChart + ? Boolean(vis.params.addTimeMarker ?? vis.type.visConfig.defaults.addTimeMarker) + : undefined, curveType: getCurveType( series[0].interpolate === InterpolationMode.StepAfter ? InterpolationMode.Linear diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts index 62130c1ecc704..35248710cff4f 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts @@ -22,7 +22,7 @@ export interface Layer { columns: Column[]; metrics: string[]; columnOrder: never[]; - seriesId: string; + seriesIdsMap: Record; isReferenceLineLayer: boolean; collapseFn?: string; } @@ -95,7 +95,17 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte supportMixedSiblingPipelineAggs: true, isPercentageMode: false, }, - visibleSeries.map((s) => ({ metrics: [s.data.id] })) + visibleSeries + .reduce>((acc, s) => { + const series = acc.find(({ type, mode }) => type === s.type && mode === s.mode); + if (series) { + series.metrics.push(s.data.id); + } else { + acc.push({ metrics: [s.data.id], type: s.type, mode: s.mode }); + } + return acc; + }, []) + .map(({ metrics }) => ({ metrics })) ); if (dataLayers === null) { @@ -140,9 +150,15 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte const layers = dataLayers.map((l) => { const layerId = uuid.default(); - const series = visibleSeries.find((s) => - l.columns.some((c) => !c.isBucketed && c.meta.aggId.split('.')[0] === s.data.id) - ); + const seriesIdsMap: Record = {}; + visibleSeries.forEach((s) => { + const column = l.columns.find( + (c) => !c.isBucketed && c.meta.aggId.split('.')[0] === s.data.id + ); + if (column) { + seriesIdsMap[column.columnId] = s.data.id; + } + }); const collapseFn = l.bucketCollapseFn ? Object.keys(l.bucketCollapseFn).find((key) => l.bucketCollapseFn[key].includes(l.buckets.customBuckets[l.metrics[0]]) @@ -154,7 +170,7 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte columns: l.columns.map(excludeMetaFromColumn), metrics: l.metrics, columnOrder: [], - seriesId: series?.data.id!, + seriesIdsMap, collapseFn, isReferenceLineLayer: false, }; @@ -170,13 +186,13 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte metrics: [staticValueColumn.columnId], isReferenceLineLayer: true, collapseFn: undefined, - seriesId: '', + seriesIdsMap: {}, }); } return { type: 'lnsXY', - layers: layers.map(({ seriesId, collapseFn, isReferenceLineLayer, ...rest }) => rest), + layers: layers.map(({ seriesIdsMap, collapseFn, isReferenceLineLayer, ...rest }) => rest), configuration: getConfiguration(layers, visibleSeries, vis), indexPatternIds: [indexPatternId], }; 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 fbc9e17d77727..2abcc3a281626 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts @@ -162,6 +162,7 @@ export interface XYConfiguration { fillOpacity?: number; hideEndzones?: boolean; valuesInLegend?: boolean; + showCurrentTimeMarker?: boolean; } export interface SortingState { From 168dd26e6a0d9e445ff441a983dfab3f21f25855 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 13 Oct 2022 14:35:22 +0300 Subject: [PATCH 18/21] Added support of assign color --- packages/kbn-optimizer/limits.yml | 2 +- .../xy/public/convert_to_lens/configurations/index.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index a93acb29096a0..fe7e876be8861 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -140,6 +140,6 @@ pageLoadAssetSize: visTypeTimeseries: 55203 visTypeVega: 153573 visTypeVislib: 242838 - visTypeXy: 31300 + visTypeXy: 31600 visualizations: 90000 watcher: 43598 diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts index b3b62ea9ba7e1..fa9cc01c6a7ca 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts @@ -111,6 +111,7 @@ function getDataLayers( series: SeriesParam[], vis: Vis ): XYDataLayerConfig[] { + const overwriteColors: Record = vis.uiState.get('vis.colors', {}); return layers.map((layer) => { const xColumn = layer.columns.find((c) => c.isBucketed && !c.isSplit); const splitAccessor = layer.columns.find( @@ -133,6 +134,7 @@ function getDataLayers( isHorizontal, isPercentage ); + return { layerId: layer.layerId, accessors: layer.metrics, @@ -150,6 +152,8 @@ function getDataLayers( return { forAccessor: metricId, axisMode: getYAxisPosition(yAxis?.position ?? 'left'), + color: + !splitAccessor && serie?.data.label ? overwriteColors[serie?.data.label] : undefined, }; }), xScaleType: getXScaleType(xColumn), From ad24cfda135cfc263a4409412b9ff0a5e822bf2d Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 13 Oct 2022 17:37:34 +0300 Subject: [PATCH 19/21] Fix terms with order by column --- .../xy/public/convert_to_lens/index.ts | 20 +++++++++---------- .../public/convert_to_lens/utils.ts | 3 ++- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts index 35248710cff4f..2d28ffe50d190 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts @@ -112,17 +112,9 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte return null; } - // doesn't support sibling pipeline aggs and split series together - if ( - visSchemas.group?.length && - dataLayers.some((l) => Object.keys(l.buckets.customBuckets).length) - ) { - return null; - } - - // doesn't support several metrics with terms split series which uses one of the metrics as order agg + // doesn't support several layers with terms split series which uses one of the metrics as order agg if ( - visSchemas.metric.length > 1 && + dataLayers.length > 1 && dataLayers.some((l) => l.columns.some( (c) => c.isSplit && 'orderBy' in c.params && c.params.orderBy.type === 'column' @@ -132,6 +124,14 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte return null; } + // doesn't support sibling pipeline aggs and split series together + if ( + visSchemas.group?.length && + dataLayers.some((l) => Object.keys(l.buckets.customBuckets).length) + ) { + return null; + } + const visibleYAxes = vis.params.valueAxes.filter((axis) => visibleSeries.some((seriesParam) => seriesParam.valueAxis === axis.id) ); diff --git a/src/plugins/visualizations/public/convert_to_lens/utils.ts b/src/plugins/visualizations/public/convert_to_lens/utils.ts index ac56501db522f..0cab4f698fb2f 100644 --- a/src/plugins/visualizations/public/convert_to_lens/utils.ts +++ b/src/plugins/visualizations/public/convert_to_lens/utils.ts @@ -124,7 +124,8 @@ export const sortColumns = ( ...acc, ...(key === 'metric' ? metricsWithoutDuplicates : visSchemas[key])?.reduce( (newAcc, schema) => { - newAcc[schema.aggId] = schema.accessor; + // metrics should always have sort more than buckets + newAcc[schema.aggId] = key === 'metric' ? schema.accessor : 1000 + schema.accessor; return newAcc; }, {} From e659105ad8a6f87195f8df5976cfb8603818b6a5 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 13 Oct 2022 17:45:12 +0300 Subject: [PATCH 20/21] Fixed sibling pipeline aggs --- .../xy/public/convert_to_lens/index.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts index 2d28ffe50d190..3b4339828c6d5 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/index.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { METRIC_TYPES } from '@kbn/data-plugin/public'; import { Column, ColumnWithMeta } from '@kbn/visualizations-plugin/common'; import { convertToLensModule, @@ -27,6 +28,13 @@ export interface Layer { collapseFn?: string; } +const SIBBLING_PIPELINE_AGGS: string[] = [ + METRIC_TYPES.AVG_BUCKET, + METRIC_TYPES.SUM_BUCKET, + METRIC_TYPES.MAX_BUCKET, + METRIC_TYPES.MIN_BUCKET, +]; + export const isColumnWithMeta = (column: Column): column is ColumnWithMeta => { if ((column as ColumnWithMeta).meta) { return true; @@ -98,7 +106,14 @@ export const convertToLens: ConvertXYToLensVisualization = async (vis, timefilte visibleSeries .reduce>((acc, s) => { const series = acc.find(({ type, mode }) => type === s.type && mode === s.mode); - if (series) { + // sibling pipeline agg always generate new layer because of custom bucket + if ( + series && + visSchemas.metric.some( + (m) => + m.aggId?.split('.')[0] === s.data.id && !SIBBLING_PIPELINE_AGGS.includes(m.aggType) + ) + ) { series.metrics.push(s.data.id); } else { acc.push({ metrics: [s.data.id], type: s.type, mode: s.mode }); From 620a802226a1a9af89dc9cc617f05aafa2cec8b1 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Fri, 14 Oct 2022 11:09:01 +0300 Subject: [PATCH 21/21] Fixed test --- packages/kbn-optimizer/limits.yml | 2 +- .../vis_types/xy/public/convert_to_lens/index.test.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index fe7e876be8861..48f5806956cf8 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -140,6 +140,6 @@ pageLoadAssetSize: visTypeTimeseries: 55203 visTypeVega: 153573 visTypeVislib: 242838 - visTypeXy: 31600 + visTypeXy: 31800 visualizations: 90000 watcher: 43598 diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/xy/public/convert_to_lens/index.test.ts index f5f1adef6f629..c77794e22ab78 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/index.test.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/index.test.ts @@ -74,12 +74,16 @@ describe('convertToLens', () => { expect(result).toBeNull(); }); - test('should return null if defined several metrics with terms split series which uses one of the metrics as order agg', async () => { + test('should return null if defined several layers with terms split series which uses one of the metrics as order agg', async () => { mockGetColumnsFromVis.mockReturnValue([ { buckets: { all: ['1'], customBuckets: { metric1: '2' } }, columns: [{ isSplit: true, params: { orderBy: { type: 'column' } } }], }, + { + buckets: { all: ['2'], customBuckets: { metric1: '2' } }, + columns: [{}], + }, ]); mockGetVisSchemas.mockReturnValue({ metric: [{ aggId: '1' }, { aggId: '2' }],