Skip to content

Commit

Permalink
[TSVB][Lens] Add "open in lens" functionality for Top N (#138200)
Browse files Browse the repository at this point in the history
* Create lens converter for top n

* Fix test

* Fix tests

* Fix comments

* Fix problem with timeseries

* Some fixes and refactoring

* Fix validation

Co-authored-by: Kibana Machine <[email protected]>
Co-authored-by: Joe Reuter <[email protected]>
  • Loading branch information
3 people authored Aug 22, 2022
1 parent b3da4e0 commit 750de11
Show file tree
Hide file tree
Showing 34 changed files with 1,271 additions and 452 deletions.
2 changes: 2 additions & 0 deletions src/plugins/data/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ import {
toAbsoluteDates,
boundsDescendingRaw,
getResponseInspectorStats,
calcAutoIntervalLessThan,
// tabify
tabifyAggResponse,
tabifyGetColumns,
Expand Down Expand Up @@ -217,6 +218,7 @@ export const search = {
termsAggFilter,
toAbsoluteDates,
boundsDescendingRaw,
calcAutoIntervalLessThan,
},
getResponseInspectorStats,
tabifyAggResponse,
Expand Down
305 changes: 3 additions & 302 deletions src/plugins/vis_types/timeseries/public/convert_to_lens/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,9 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { DataView } from '@kbn/data-plugin/common';
import type { Panel, Series } from '../../common/types';
import { convertTSVBtoLensConfiguration } from '.';

const dataViewsMap: Record<string, DataView> = {
test1: { id: 'test1', title: 'test1', timeFieldName: 'timeField1' } as DataView,
test2: {
id: 'test2',
title: 'test2',
timeFieldName: 'timeField2',
} as DataView,
test3: { id: 'test3', title: 'test3', timeFieldName: 'timeField3' } as DataView,
};

const getDataview = (id: string): DataView | undefined => dataViewsMap[id];
jest.mock('../services', () => {
return {
getDataViewsStart: jest.fn(() => {
return {
getDefault: jest.fn(() => {
return { id: '12345', title: 'default', timeFieldName: '@timestamp' };
}),
get: getDataview,
};
}),
};
});
import type { Panel } from '../../common/types';
import { convertTSVBtoLensConfiguration } from '.';

const model = {
axis_position: 'left',
Expand Down Expand Up @@ -61,7 +37,7 @@ const model = {
} as Panel;

describe('convertTSVBtoLensConfiguration', () => {
test('should return null for a non timeseries chart', async () => {
test('should return null for a not supported chart', async () => {
const metricModel = {
...model,
type: 'metric',
Expand All @@ -78,279 +54,4 @@ describe('convertTSVBtoLensConfiguration', () => {
const triggerOptions = await convertTSVBtoLensConfiguration(stringIndexPatternModel);
expect(triggerOptions).toBeNull();
});

test('should return null for a non supported aggregation', async () => {
const nonSupportedAggModel = {
...model,
series: [
{
...model.series[0],
metrics: [
{
type: 'std_deviation',
},
] as Series['metrics'],
},
],
};
const triggerOptions = await convertTSVBtoLensConfiguration(nonSupportedAggModel);
expect(triggerOptions).toBeNull();
});

test('should return options for a supported aggregation', async () => {
const triggerOptions = await convertTSVBtoLensConfiguration(model);
expect(triggerOptions).toStrictEqual({
configuration: {
extents: { yLeftExtent: { mode: 'full' }, yRightExtent: { mode: 'full' } },
fill: '0',
gridLinesVisibility: { x: false, yLeft: false, yRight: false },
legend: {
isVisible: false,
maxLines: 1,
position: 'right',
shouldTruncate: false,
showSingleSeries: false,
},
},
type: 'lnsXY',
layers: {
'0': {
axisPosition: 'left',
chartType: 'line',
collapseFn: undefined,
indexPatternId: 'test2',
metrics: [
{
agg: 'count',
color: '#000000',
fieldName: 'document',
isFullReference: false,
params: {},
},
],
palette: {
name: 'default',
type: 'palette',
},
splitWithDateHistogram: false,
timeFieldName: 'timeField2',
timeInterval: 'auto',
dropPartialBuckets: false,
},
},
});
});

test('should return area for timeseries line chart with fill > 0', async () => {
const modelWithFill = {
...model,
series: [
{
...model.series[0],
fill: '0.3',
stacked: 'none',
},
],
};
const triggerOptions = await convertTSVBtoLensConfiguration(modelWithFill);
expect(triggerOptions?.layers[0].chartType).toBe('area');
});

test('should return timeShift in the params if it is provided', async () => {
const modelWithFill = {
...model,
series: [
{
...model.series[0],
offset_time: '1h',
},
],
};
const triggerOptions = await convertTSVBtoLensConfiguration(modelWithFill);
expect(triggerOptions?.layers[0]?.metrics?.[0]?.params?.shift).toBe('1h');
});

test('should return filter in the params if it is provided', async () => {
const modelWithFill = {
...model,
series: [
{
...model.series[0],
filter: {
language: 'kuery',
query: 'test',
},
},
],
};
const triggerOptions = await convertTSVBtoLensConfiguration(modelWithFill);
expect(triggerOptions?.layers[0]?.metrics?.[0]?.params?.kql).toBe('test');
});

test('should return splitFilters information if the chart is broken down by filters', async () => {
const modelWithSplitFilters = {
...model,
series: [
{
...model.series[0],
split_mode: 'filters',
split_filters: [
{
color: 'rgba(188,0,85,1)',
filter: {
language: 'kuery',
query: '',
},
id: '89afac60-7d2b-11ec-917c-c18cd38d60b5',
},
],
},
],
};
const triggerOptions = await convertTSVBtoLensConfiguration(modelWithSplitFilters);
expect(triggerOptions?.layers[0]?.splitFilters).toStrictEqual([
{
color: 'rgba(188,0,85,1)',
filter: {
language: 'kuery',
query: '',
},
id: '89afac60-7d2b-11ec-917c-c18cd38d60b5',
},
]);
});

test('should return termsParams information if the chart is broken down by terms including series agg collapse fn', async () => {
const modelWithTerms = {
...model,
series: [
{
...model.series[0],
metrics: [
...model.series[0].metrics,
{
type: 'series_agg',
function: 'sum',
},
],
split_mode: 'terms',
terms_size: 6,
terms_direction: 'desc',
terms_order_by: '_key',
},
] as unknown as Series[],
};
const triggerOptions = await convertTSVBtoLensConfiguration(modelWithTerms);
expect(triggerOptions?.layers[0]?.collapseFn).toStrictEqual('sum');
expect(triggerOptions?.layers[0]?.termsParams).toStrictEqual({
size: 6,
otherBucket: false,
orderDirection: 'desc',
orderBy: { type: 'alphabetical' },
includeIsRegex: false,
excludeIsRegex: false,
parentFormat: {
id: 'terms',
},
});
});

test('should return include exclude information if the chart is broken down by terms', async () => {
const modelWithTerms = {
...model,
series: [
{
...model.series[0],
split_mode: 'terms',
terms_size: 6,
terms_direction: 'desc',
terms_order_by: '_key',
terms_include: 't.*',
},
] as unknown as Series[],
};
const triggerOptions = await convertTSVBtoLensConfiguration(modelWithTerms);
expect(triggerOptions?.layers[0]?.termsParams).toStrictEqual({
size: 6,
otherBucket: false,
orderDirection: 'desc',
orderBy: { type: 'alphabetical' },
includeIsRegex: true,
include: ['t.*'],
excludeIsRegex: false,
parentFormat: {
id: 'terms',
},
});
});

test('should return custom time interval if it is given', async () => {
const modelWithTerms = {
...model,
interval: '1h',
};
const triggerOptions = await convertTSVBtoLensConfiguration(modelWithTerms);
expect(triggerOptions?.layers[0]?.timeInterval).toBe('1h');
});

test('should return dropPartialbuckets if enabled', async () => {
const modelWithDropBuckets = {
...model,
drop_last_bucket: 1,
};
const triggerOptions = await convertTSVBtoLensConfiguration(modelWithDropBuckets);
expect(triggerOptions?.layers[0]?.dropPartialBuckets).toBe(true);
});

test('should return the correct chart configuration', async () => {
const modelWithConfig = {
...model,
show_legend: 1,
legend_position: 'bottom',
truncate_legend: 0,
show_grid: 1,
series: [{ ...model.series[0], fill: '0.3', separate_axis: 1, axis_position: 'right' }],
};
const triggerOptions = await convertTSVBtoLensConfiguration(modelWithConfig);
expect(triggerOptions).toStrictEqual({
configuration: {
extents: { yLeftExtent: { mode: 'full' }, yRightExtent: { mode: 'full' } },
fill: '0.3',
gridLinesVisibility: { x: true, yLeft: true, yRight: true },
legend: {
isVisible: true,
maxLines: 1,
position: 'bottom',
shouldTruncate: false,
showSingleSeries: true,
},
},
type: 'lnsXY',
layers: {
'0': {
axisPosition: 'right',
chartType: 'area_stacked',
collapseFn: undefined,
indexPatternId: 'test2',
metrics: [
{
agg: 'count',
color: '#000000',
fieldName: 'document',
isFullReference: false,
params: {},
},
],
palette: {
name: 'default',
type: 'palette',
},
splitWithDateHistogram: false,
timeFieldName: 'timeField2',
timeInterval: 'auto',
dropPartialBuckets: false,
},
},
});
});
});
16 changes: 13 additions & 3 deletions src/plugins/vis_types/timeseries/public/convert_to_lens/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Side Public License, v 1.
*/

import { TimeRange } from '@kbn/data-plugin/common';
import type { Panel } from '../../common/types';
import { PANEL_TYPES } from '../../common/enums';
import { ConvertTsvbToLensVisualization } from './types';
Expand All @@ -18,6 +19,10 @@ const getConvertFnByType = (
const { convertToLens } = await import('./timeseries');
return convertToLens;
},
[PANEL_TYPES.TOP_N]: async () => {
const { convertToLens } = await import('./top_n');
return convertToLens;
},
};

return convertionFns[type]?.();
Expand All @@ -28,12 +33,17 @@ const getConvertFnByType = (
* Returns the Lens model, only if it is supported. If not, it returns null.
* In case of null, the menu item is disabled and the user can't navigate to Lens.
*/
export const convertTSVBtoLensConfiguration = async (model: Panel) => {
// Disables the option for not timeseries charts, for the string mode and for series with annotations
export const convertTSVBtoLensConfiguration = async (model: Panel, timeRange?: TimeRange) => {
// Disables the option for not supported charts, for the string mode and for series with annotations
if (!model.use_kibana_indexes || (model.annotations && model.annotations.length > 0)) {
return null;
}
// Disables if model is invalid
if (model.isModelInvalid) {
return null;
}

const convertFn = await getConvertFnByType(model.type);

return (await convertFn?.(model)) ?? null;
return (await convertFn?.(model, timeRange)) ?? null;
};
Loading

0 comments on commit 750de11

Please sign in to comment.