[TSVB][Lens]Fix conversion from static value in timeseries to reference line in lens (#142453) (#142596)

* Fix conversion static value in timeseries to reference line in lens

* Doesn't allow convert static value with split

* Fix condition

* Ignore axis position from model for top n

* Added tests

Co-authored-by: Stratoula Kalafateli <efstratia.kalafateli@elastic.co>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
(cherry picked from commit 8e770bb608)

# Conflicts:
#	src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts
#	src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts
#	src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts
This commit is contained in:
Uladzislau Lasitsa 2022-10-04 16:56:45 +03:00 committed by GitHub
parent e55eebd128
commit 99d6218b88
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 122 additions and 46 deletions

View file

@ -24,6 +24,33 @@ jest.mock('uuid', () => ({
v4: () => 'test-id',
}));
const mockedIndices = [
{
id: 'test',
title: 'test',
timeFieldName: 'test_field',
getFieldByName: (name: string) => ({ aggregatable: name !== 'host' }),
},
] as unknown as DataView[];
const indexPatternsService = {
getDefault: jest.fn(() =>
Promise.resolve({
id: 'default',
title: 'index',
getFieldByName: (name: string) => ({ aggregatable: name !== 'host' }),
})
),
get: jest.fn((id) => Promise.resolve({ ...mockedIndices[0], id })),
find: jest.fn((search: string, size: number) => {
if (size !== 1) {
// shouldn't request more than one data view since there is a significant performance penalty
throw new Error('trying to fetch too many data views');
}
return Promise.resolve(mockedIndices || []);
}),
} as unknown as DataViewsPublicPluginStart;
describe('getLayers', () => {
const dataSourceLayers: Record<number, Layer> = [
{
@ -285,10 +312,16 @@ describe('getLayers', () => {
series: [createSeries({ metrics: staticValueMetric })],
});
test.each<[string, [Record<number, Layer>, Panel], Array<Partial<XYLayerConfig>>]>([
test.each<
[
string,
[Record<number, Layer>, Panel, DataViewsPublicPluginStart, boolean],
Array<Partial<XYLayerConfig>>
]
>([
[
'data layer if columns do not include static column',
[dataSourceLayers, panel],
[dataSourceLayers, panel, indexPatternsService, false],
[
{
layerType: 'data',
@ -307,9 +340,30 @@ describe('getLayers', () => {
},
],
],
[
'data layer with "left" axisMode if isSingleAxis is provided',
[dataSourceLayers, panel, indexPatternsService, true],
[
{
layerType: 'data',
accessors: ['column-id-1'],
xAccessor: 'column-id-2',
splitAccessor: 'column-id-3',
seriesType: 'area',
layerId: 'test-layer-1',
yConfig: [
{
forAccessor: 'column-id-1',
axisMode: 'left',
color: '#68BC00',
},
],
},
],
],
[
'reference line layer if columns include static column',
[dataSourceLayersWithStatic, panelWithStaticValue],
[dataSourceLayersWithStatic, panelWithStaticValue, indexPatternsService, false],
[
{
layerType: 'referenceLine',
@ -318,9 +372,10 @@ describe('getLayers', () => {
yConfig: [
{
forAccessor: 'column-id-1',
axisMode: 'right',
axisMode: 'left',
color: '#68BC00',
fill: 'below',
lineWidth: 1,
},
],
},
@ -328,7 +383,7 @@ describe('getLayers', () => {
],
[
'correct colors if columns include percentile columns',
[dataSourceLayersWithPercentile, panelWithPercentileMetric],
[dataSourceLayersWithPercentile, panelWithPercentileMetric, indexPatternsService, false],
[
{
yConfig: [
@ -348,7 +403,12 @@ describe('getLayers', () => {
],
[
'correct colors if columns include percentile rank columns',
[dataSourceLayersWithPercentileRank, panelWithPercentileRankMetric],
[
dataSourceLayersWithPercentileRank,
panelWithPercentileRankMetric,
indexPatternsService,
false,
],
[
{
yConfig: [
@ -368,7 +428,7 @@ describe('getLayers', () => {
],
[
'annotation layer gets correct params and converts color, extraFields and icons',
[dataSourceLayersWithStatic, panelWithSingleAnnotation],
[dataSourceLayersWithStatic, panelWithSingleAnnotation, indexPatternsService, false],
[
{
layerType: 'referenceLine',
@ -377,9 +437,10 @@ describe('getLayers', () => {
yConfig: [
{
forAccessor: 'column-id-1',
axisMode: 'right',
axisMode: 'left',
color: '#68BC00',
fill: 'below',
lineWidth: 1,
},
],
},
@ -413,7 +474,7 @@ describe('getLayers', () => {
],
[
'multiple annotations with different data views create separate layers',
[dataSourceLayersWithStatic, panelWithMultiAnnotations],
[dataSourceLayersWithStatic, panelWithMultiAnnotations, indexPatternsService, false],
[
{
layerType: 'referenceLine',
@ -422,9 +483,10 @@ describe('getLayers', () => {
yConfig: [
{
forAccessor: 'column-id-1',
axisMode: 'right',
axisMode: 'left',
color: '#68BC00',
fill: 'below',
lineWidth: 1,
},
],
},
@ -498,27 +560,7 @@ describe('getLayers', () => {
],
],
])('should return %s', async (_, input, expected) => {
const layers = await getLayers(...input, indexPatternsService as DataViewsPublicPluginStart);
const layers = await getLayers(...input);
expect(layers).toEqual(expected.map(expect.objectContaining));
});
});
const mockedIndices = [
{
id: 'test',
title: 'test',
getFieldByName: (name: string) => ({ aggregatable: name !== 'host' }),
},
] as unknown as DataView[];
const indexPatternsService = {
getDefault: jest.fn(() => Promise.resolve({ id: 'default', title: 'index' })),
get: jest.fn(() => Promise.resolve(mockedIndices[0])),
find: jest.fn((search: string, size: number) => {
if (size !== 1) {
// shouldn't request more than one data view since there is a significant performance penalty
throw new Error('trying to fetch too many data views');
}
return Promise.resolve(mockedIndices || []);
}),
} as unknown as DataViewsPublicPluginStart;

View file

@ -23,7 +23,7 @@ import { DataViewsPublicPluginStart, DataView } from '@kbn/data-plugin/public/da
import { fetchIndexPattern } from '../../../../../common/index_patterns_utils';
import { ICON_TYPES_MAP } from '../../../../application/visualizations/constants';
import { SUPPORTED_METRICS } from '../../metrics';
import type { Annotation, Metric, Panel } from '../../../../../common/types';
import type { Annotation, Metric, Panel, Series } from '../../../../../common/types';
import { getSeriesAgg } from '../../series';
import {
isPercentileRanksColumnWithMeta,
@ -43,6 +43,10 @@ function getPalette(palette: PaletteOutput): PaletteOutput {
: palette;
}
function getAxisMode(series: Series, model: Panel): YAxisMode {
return (series.separate_axis ? series.axis_position : model.axis_position) as YAxisMode;
}
function getColor(
metricColumn: Column,
metric: Metric,
@ -68,7 +72,8 @@ function nonNullable<T>(value: T): value is NonNullable<T> {
export const getLayers = async (
dataSourceLayers: Record<number, Layer>,
model: Panel,
dataViews: DataViewsPublicPluginStart
dataViews: DataViewsPublicPluginStart,
isSingleAxis: boolean = false
): Promise<XYLayerConfig[]> => {
const nonAnnotationsLayers: XYLayerConfig[] = Object.keys(dataSourceLayers).map((key) => {
const series = model.series[parseInt(key, 10)];
@ -83,13 +88,13 @@ export const getLayers = async (
const metricColumns = dataSourceLayer.columns.filter(
(l) => !l.isBucketed && l.columnId !== referenceColumnId
);
const isReferenceLine = metrics.length === 1 && metrics[0].type === 'static';
const isReferenceLine =
metricColumns.length === 1 && metricColumns[0].operationType === 'static_value';
const splitAccessor = dataSourceLayer.columns.find(
(column) => column.isBucketed && column.isSplit
)?.columnId;
const chartType = getChartType(series, model.type);
const commonProps = {
seriesType: chartType,
layerId: dataSourceLayer.layerId,
accessors: metricColumns.map((metricColumn) => {
return metricColumn.columnId;
@ -101,19 +106,19 @@ export const getLayers = async (
return {
forAccessor: metricColumn.columnId,
color: getColor(metricColumn, metric!, series.color, splitAccessor),
axisMode: (series.separate_axis
? series.axis_position
: model.axis_position) as YAxisMode,
axisMode: isReferenceLine // reference line should be assigned to axis with real data
? model.series.some((s) => s.id !== series.id && getAxisMode(s, model) === 'right')
? 'right'
: 'left'
: isSingleAxis
? 'left'
: getAxisMode(series, model),
...(isReferenceLine && {
fill: chartType === 'area' ? FillTypes.BELOW : FillTypes.NONE,
fill: chartType.includes('area') ? FillTypes.BELOW : FillTypes.NONE,
lineWidth: series.line_width,
}),
};
}),
xAccessor: dataSourceLayer.columns.find((column) => column.isBucketed && !column.isSplit)
?.columnId,
splitAccessor,
collapseFn: seriesAgg,
palette: getPalette(series.palette as PaletteOutput),
};
if (isReferenceLine) {
return {
@ -122,8 +127,14 @@ export const getLayers = async (
};
} else {
return {
seriesType: chartType,
layerType: 'data',
...commonProps,
xAccessor: dataSourceLayer.columns.find((column) => column.isBucketed && !column.isSplit)
?.columnId,
splitAccessor,
collapseFn: seriesAgg,
palette: getPalette(series.palette as PaletteOutput),
};
}
});

View file

@ -112,6 +112,19 @@ describe('convertToLens', () => {
expect(mockGetBucketsColumns).toBeCalledTimes(1);
});
test('should return null for static value with buckets', async () => {
mockGetBucketsColumns.mockReturnValue([{}]);
mockGetMetricsColumns.mockReturnValue([
{
operationType: 'static_value',
},
]);
const result = await convertToLens(model);
expect(result).toBeNull();
expect(mockGetMetricsColumns).toBeCalledTimes(1);
expect(mockGetBucketsColumns).toBeCalledTimes(1);
});
test('should return state for valid model', async () => {
const result = await convertToLens(model);
expect(result).toBeDefined();

View file

@ -88,11 +88,21 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model: Panel
return null;
}
const isReferenceLine =
metricsColumns.length === 1 && metricsColumns[0].operationType === 'static_value';
// only static value without split is supported
if (isReferenceLine && bucketsColumns.length) {
return null;
}
const layerId = uuid();
extendedLayers[layerIdx] = {
indexPatternId,
layerId,
columns: [...metricsColumns, dateHistogramColumn, ...bucketsColumns],
columns: isReferenceLine
? [...metricsColumns]
: [...metricsColumns, dateHistogramColumn, ...bucketsColumns],
columnOrder: [],
};
}

View file

@ -79,7 +79,7 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model, timeR
};
}
const configLayers = await getLayers(extendedLayers, model, dataViews);
const configLayers = await getLayers(extendedLayers, model, dataViews, true);
return {
type: 'lnsXY',