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

* 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>
This commit is contained in:
Uladzislau Lasitsa 2022-10-04 15:00:59 +03:00 committed by GitHub
parent 8be7668d20
commit 8e770bb608
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 138 additions and 57 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> = [
{
@ -331,10 +358,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',
@ -353,9 +386,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',
@ -364,9 +418,10 @@ describe('getLayers', () => {
yConfig: [
{
forAccessor: 'column-id-1',
axisMode: 'right',
axisMode: 'left',
color: '#68BC00',
fill: 'below',
lineWidth: 1,
},
],
},
@ -374,7 +429,7 @@ describe('getLayers', () => {
],
[
'correct colors if columns include percentile columns',
[dataSourceLayersWithPercentile, panelWithPercentileMetric],
[dataSourceLayersWithPercentile, panelWithPercentileMetric, indexPatternsService, false],
[
{
yConfig: [
@ -394,7 +449,12 @@ describe('getLayers', () => {
],
[
'correct colors if columns include percentile rank columns',
[dataSourceLayersWithPercentileRank, panelWithPercentileRankMetric],
[
dataSourceLayersWithPercentileRank,
panelWithPercentileRankMetric,
indexPatternsService,
false,
],
[
{
yConfig: [
@ -414,7 +474,7 @@ describe('getLayers', () => {
],
[
'annotation layer gets correct params and converts color, extraFields and icons',
[dataSourceLayersWithStatic, panelWithSingleAnnotation],
[dataSourceLayersWithStatic, panelWithSingleAnnotation, indexPatternsService, false],
[
{
layerType: 'referenceLine',
@ -423,9 +483,10 @@ describe('getLayers', () => {
yConfig: [
{
forAccessor: 'column-id-1',
axisMode: 'right',
axisMode: 'left',
color: '#68BC00',
fill: 'below',
lineWidth: 1,
},
],
},
@ -459,7 +520,12 @@ describe('getLayers', () => {
],
[
'annotation layer should gets correct default params',
[dataSourceLayersWithStatic, panelWithSingleAnnotationWithoutQueryStringAndTimefield],
[
dataSourceLayersWithStatic,
panelWithSingleAnnotationWithoutQueryStringAndTimefield,
indexPatternsService,
false,
],
[
{
layerType: 'referenceLine',
@ -468,9 +534,10 @@ describe('getLayers', () => {
yConfig: [
{
forAccessor: 'column-id-1',
axisMode: 'right',
axisMode: 'left',
color: '#68BC00',
fill: 'below',
lineWidth: 1,
},
],
},
@ -504,7 +571,7 @@ describe('getLayers', () => {
],
[
'multiple annotations with different data views create separate layers',
[dataSourceLayersWithStatic, panelWithMultiAnnotations],
[dataSourceLayersWithStatic, panelWithMultiAnnotations, indexPatternsService, false],
[
{
layerType: 'referenceLine',
@ -513,9 +580,10 @@ describe('getLayers', () => {
yConfig: [
{
forAccessor: 'column-id-1',
axisMode: 'right',
axisMode: 'left',
color: '#68BC00',
fill: 'below',
lineWidth: 1,
},
],
},
@ -598,7 +666,12 @@ describe('getLayers', () => {
],
[
'annotation layer gets correct dataView when none is defined',
[dataSourceLayersWithStatic, panelWithSingleAnnotationDefaultDataView],
[
dataSourceLayersWithStatic,
panelWithSingleAnnotationDefaultDataView,
indexPatternsService,
false,
],
[
{
layerType: 'referenceLine',
@ -607,9 +680,10 @@ describe('getLayers', () => {
yConfig: [
{
forAccessor: 'column-id-1',
axisMode: 'right',
axisMode: 'left',
color: '#68BC00',
fill: 'below',
lineWidth: 1,
},
],
},
@ -642,34 +716,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',
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;

View file

@ -24,7 +24,7 @@ import { getDefaultQueryLanguage } from '../../../../application/components/lib/
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,
@ -44,6 +44,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,
@ -69,7 +73,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[] | null> => {
const nonAnnotationsLayers: XYLayerConfig[] = Object.keys(dataSourceLayers).map((key) => {
const series = model.series[parseInt(key, 10)];
@ -84,13 +89,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;
@ -102,19 +107,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 {
@ -123,8 +128,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

@ -98,11 +98,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

@ -86,7 +86,7 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model, timeR
};
}
const configLayers = await getLayers(extendedLayers, model, dataViews);
const configLayers = await getLayers(extendedLayers, model, dataViews, true);
if (configLayers === null) {
return null;
}