mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Agg based pie][Lens]Navigate to lens Agg based pie (#140879)
* Added support for converting Agg-based pie to Lens. * Fixed problem with removed index pattern. * Made some changes for uniformity. * Fixed label of go back to. * Fixed legend display if uiState was changed. * Fixed configuration. * Removed as string[]. * Fixed code according to the nit. * Allowed 3 segments and restricted converting of split_row/split_column. * Fixed tests. * Refactored functional tests. * Added functional tests for pie. Co-authored-by: Yaroslav Kuznietsov <kuznetsov.yaroslav.yk@gmail.com> Co-authored-by: Joe Reuter <johannes.reuter@elastic.co>
This commit is contained in:
parent
45e80b2a47
commit
ea046acd24
38 changed files with 1010 additions and 275 deletions
|
@ -26,6 +26,7 @@ Object {
|
|||
"as": "partitionVis",
|
||||
"type": "render",
|
||||
"value": Object {
|
||||
"canNavigateToLens": false,
|
||||
"params": Object {
|
||||
"listenOnChange": true,
|
||||
},
|
||||
|
@ -160,6 +161,7 @@ Object {
|
|||
"as": "partitionVis",
|
||||
"type": "render",
|
||||
"value": Object {
|
||||
"canNavigateToLens": false,
|
||||
"params": Object {
|
||||
"listenOnChange": true,
|
||||
},
|
||||
|
|
|
@ -188,6 +188,7 @@ export const pieVisFunction = (): PieVisExpressionFunctionDefinition => ({
|
|||
visConfig,
|
||||
syncColors: handlers?.isSyncColorsEnabled?.() ?? false,
|
||||
visType: args.isDonut ? ChartTypes.DONUT : ChartTypes.PIE,
|
||||
canNavigateToLens: Boolean(handlers?.variables?.canNavigateToLens),
|
||||
params: {
|
||||
listenOnChange: true,
|
||||
},
|
||||
|
|
|
@ -108,6 +108,7 @@ export interface RenderValue {
|
|||
visType: ChartTypes;
|
||||
visConfig: PartitionVisParams;
|
||||
syncColors: boolean;
|
||||
canNavigateToLens?: boolean;
|
||||
}
|
||||
|
||||
export enum LabelPositions {
|
||||
|
|
|
@ -49,7 +49,11 @@ export const getPartitionVisRenderer: (
|
|||
displayName: strings.getDisplayName(),
|
||||
help: strings.getHelpDescription(),
|
||||
reuseDomNode: true,
|
||||
render: async (domNode, { visConfig, visData, visType, syncColors }, handlers) => {
|
||||
render: async (
|
||||
domNode,
|
||||
{ visConfig, visData, visType, syncColors, canNavigateToLens },
|
||||
handlers
|
||||
) => {
|
||||
const { core, plugins } = getStartDeps();
|
||||
|
||||
handlers.onDestroy(() => {
|
||||
|
@ -62,9 +66,12 @@ export const getPartitionVisRenderer: (
|
|||
const visualizationType = extractVisualizationType(executionContext);
|
||||
|
||||
if (containerType && visualizationType) {
|
||||
plugins.usageCollection?.reportUiCounter(containerType, METRIC_TYPE.COUNT, [
|
||||
const events = [
|
||||
`render_${visualizationType}_${visType}`,
|
||||
]);
|
||||
canNavigateToLens ? `render_${visualizationType}_${visType}_convertable` : undefined,
|
||||
].filter<string>((event): event is string => Boolean(event));
|
||||
|
||||
plugins.usageCollection?.reportUiCounter(containerType, METRIC_TYPE.COUNT, events);
|
||||
}
|
||||
handlers.done();
|
||||
};
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
"version": "kibana",
|
||||
"ui": true,
|
||||
"server": true,
|
||||
"requiredPlugins": ["charts", "data", "expressions", "visualizations", "usageCollection", "expressionPartitionVis"],
|
||||
"requiredBundles": ["visDefaultEditor"],
|
||||
"requiredPlugins": ["charts", "data", "expressions", "visualizations", "usageCollection", "expressionPartitionVis", "dataViews"],
|
||||
"requiredBundles": ["visDefaultEditor", "kibanaUtils"],
|
||||
"extraPublicDirs": ["common/index"],
|
||||
"owner": {
|
||||
"name": "Vis Editors",
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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 { getConfiguration } from '.';
|
||||
import { samplePieVis } from '../../sample_vis.test.mocks';
|
||||
|
||||
describe('getConfiguration', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('should return correct configuration', () => {
|
||||
samplePieVis.uiState.get.mockReturnValueOnce(undefined);
|
||||
expect(
|
||||
getConfiguration('test1', samplePieVis as any, {
|
||||
metrics: ['metric-1'],
|
||||
buckets: ['bucket-1'],
|
||||
})
|
||||
).toEqual({
|
||||
layers: [
|
||||
{
|
||||
categoryDisplay: undefined,
|
||||
emptySizeRatio: undefined,
|
||||
layerId: 'test1',
|
||||
layerType: 'data',
|
||||
legendDisplay: 'show',
|
||||
legendMaxLines: 1,
|
||||
legendPosition: 'right',
|
||||
legendSize: 'large',
|
||||
metric: 'metric-1',
|
||||
nestedLegend: true,
|
||||
numberDisplay: 'percent',
|
||||
percentDecimals: 2,
|
||||
primaryGroups: ['bucket-1'],
|
||||
secondaryGroups: [],
|
||||
showValuesInLegend: true,
|
||||
truncateLegend: true,
|
||||
},
|
||||
],
|
||||
shape: 'donut',
|
||||
palette: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
test('should return legendDisplay = show if uiState contains truthy value', () => {
|
||||
samplePieVis.uiState.get.mockReturnValueOnce(true);
|
||||
expect(
|
||||
getConfiguration(
|
||||
'test1',
|
||||
{ ...samplePieVis, params: { ...samplePieVis.params, legendDisplay: 'hide' } } as any,
|
||||
{
|
||||
metrics: ['metric-1'],
|
||||
buckets: ['bucket-1'],
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
layers: [expect.objectContaining({ legendDisplay: 'show' })],
|
||||
shape: 'donut',
|
||||
palette: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
test('should return legendDisplay = hide if uiState contains falsy value', () => {
|
||||
samplePieVis.uiState.get.mockReturnValueOnce(false);
|
||||
expect(
|
||||
getConfiguration(
|
||||
'test1',
|
||||
{ ...samplePieVis, params: { ...samplePieVis.params, legendDisplay: 'show' } } as any,
|
||||
{
|
||||
metrics: ['metric-1'],
|
||||
buckets: ['bucket-1'],
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
layers: [expect.objectContaining({ legendDisplay: 'hide' })],
|
||||
shape: 'donut',
|
||||
palette: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
test('should return value of legendDisplay if uiState contains undefined value', () => {
|
||||
samplePieVis.uiState.get.mockReturnValueOnce(undefined);
|
||||
const legendDisplay = 'show';
|
||||
expect(
|
||||
getConfiguration(
|
||||
'test1',
|
||||
{ ...samplePieVis, params: { ...samplePieVis.params, legendDisplay } } as any,
|
||||
{
|
||||
metrics: ['metric-1'],
|
||||
buckets: ['bucket-1'],
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
layers: [expect.objectContaining({ legendDisplay })],
|
||||
shape: 'donut',
|
||||
palette: undefined,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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 { LegendDisplay, PartitionVisParams } from '@kbn/expression-partition-vis-plugin/common';
|
||||
import {
|
||||
CategoryDisplayTypes,
|
||||
NumberDisplayTypes,
|
||||
PartitionVisConfiguration,
|
||||
} from '@kbn/visualizations-plugin/common/convert_to_lens';
|
||||
import { Vis } from '@kbn/visualizations-plugin/public';
|
||||
|
||||
const getLayers = (
|
||||
layerId: string,
|
||||
vis: Vis<PartitionVisParams>,
|
||||
metrics: string[],
|
||||
buckets: string[]
|
||||
): PartitionVisConfiguration['layers'] => {
|
||||
const legendOpen = vis.uiState.get('vis.legendOpen');
|
||||
const legendDisplayFromUiState =
|
||||
legendOpen !== undefined ? (legendOpen ? LegendDisplay.SHOW : LegendDisplay.HIDE) : undefined;
|
||||
|
||||
const showValuesInLegend =
|
||||
vis.params.labels.values ??
|
||||
vis.params.showValuesInLegend ??
|
||||
vis.type.visConfig.defaults.showValuesInLegend;
|
||||
|
||||
return [
|
||||
{
|
||||
layerId,
|
||||
layerType: 'data' as const,
|
||||
primaryGroups: buckets,
|
||||
secondaryGroups: [],
|
||||
metric: metrics[0],
|
||||
numberDisplay:
|
||||
showValuesInLegend === false
|
||||
? NumberDisplayTypes.HIDDEN
|
||||
: vis.params.labels.valuesFormat ?? vis.type.visConfig.defaults.labels.valuesFormat,
|
||||
categoryDisplay: vis.params.labels.show
|
||||
? vis.params.labels.position ?? vis.type.visConfig.defaults.labels.position
|
||||
: CategoryDisplayTypes.HIDE,
|
||||
legendDisplay:
|
||||
legendDisplayFromUiState ??
|
||||
vis.params.legendDisplay ??
|
||||
vis.type.visConfig.defaults.legendDisplay,
|
||||
legendPosition: vis.params.legendPosition ?? vis.type.visConfig.defaults.legendPosition,
|
||||
showValuesInLegend,
|
||||
nestedLegend: vis.params.nestedLegend ?? vis.type.visConfig.defaults.nestedLegend,
|
||||
percentDecimals:
|
||||
vis.params.labels.percentDecimals ?? vis.type.visConfig.defaults.labels.percentDecimals,
|
||||
emptySizeRatio: vis.params.emptySizeRatio ?? vis.type.visConfig.defaults.emptySizeRatio,
|
||||
legendMaxLines: vis.params.maxLegendLines ?? vis.type.visConfig.defaults.maxLegendLines,
|
||||
legendSize: vis.params.legendSize ?? vis.type.visConfig.defaults.legendSize,
|
||||
truncateLegend: vis.params.truncateLegend ?? vis.type.visConfig.defaults.truncateLegend,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const getConfiguration = (
|
||||
layerId: string,
|
||||
vis: Vis<PartitionVisParams>,
|
||||
{
|
||||
metrics,
|
||||
buckets,
|
||||
}: {
|
||||
metrics: string[];
|
||||
buckets: string[];
|
||||
}
|
||||
): PartitionVisConfiguration => {
|
||||
return {
|
||||
shape: vis.params.isDonut ? 'donut' : 'pie',
|
||||
layers: getLayers(layerId, vis, metrics, buckets),
|
||||
palette: vis.params.palette,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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 { samplePieVis } from '../sample_vis.test.mocks';
|
||||
|
||||
const mockGetColumnsFromVis = jest.fn();
|
||||
const mockGetConfiguration = jest.fn().mockReturnValue({});
|
||||
|
||||
jest.mock('../services', () => ({
|
||||
getDataViewsStart: jest.fn(() => ({ get: () => ({}), getDefault: () => ({}) })),
|
||||
}));
|
||||
|
||||
jest.mock('@kbn/visualizations-plugin/public', () => ({
|
||||
convertToLensModule: Promise.resolve({
|
||||
getColumnsFromVis: jest.fn(() => mockGetColumnsFromVis()),
|
||||
}),
|
||||
getDataViewByIndexPatternId: jest.fn(() => ({ id: 'index-pattern' })),
|
||||
}));
|
||||
|
||||
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(samplePieVis as any, {} as any);
|
||||
expect(mockGetColumnsFromVis).toBeCalledTimes(1);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test('should return null if more than three split slice levels', async () => {
|
||||
mockGetColumnsFromVis.mockReturnValue({
|
||||
buckets: ['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: [],
|
||||
});
|
||||
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: ['2'],
|
||||
columns: [{ columnId: '2' }, { columnId: '1' }],
|
||||
});
|
||||
const result = await convertToLens(samplePieVis as any, {} as any);
|
||||
expect(mockGetColumnsFromVis).toBeCalledTimes(1);
|
||||
expect(mockGetConfiguration).toBeCalledTimes(1);
|
||||
expect(result?.type).toEqual('lnsPie');
|
||||
expect(result?.layers.length).toEqual(1);
|
||||
expect(result?.layers[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
columnOrder: [],
|
||||
columns: [{ columnId: '2' }, { columnId: '1' }],
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
79
src/plugins/vis_types/pie/public/convert_to_lens/index.ts
Normal file
79
src/plugins/vis_types/pie/public/convert_to_lens/index.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Column, ColumnWithMeta } from '@kbn/visualizations-plugin/common';
|
||||
import {
|
||||
convertToLensModule,
|
||||
getDataViewByIndexPatternId,
|
||||
} from '@kbn/visualizations-plugin/public';
|
||||
import uuid from 'uuid';
|
||||
import { getDataViewsStart } from '../services';
|
||||
import { getConfiguration } from './configurations';
|
||||
import { ConvertPieToLensVisualization } 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: ConvertPieToLensVisualization = 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 } = await convertToLensModule;
|
||||
const result = getColumnsFromVis(vis, timefilter, dataView, {
|
||||
buckets: [],
|
||||
splits: ['segment'],
|
||||
unsupported: ['split_row', 'split_column'],
|
||||
});
|
||||
|
||||
if (result === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const layerId = uuid();
|
||||
|
||||
const indexPatternId = dataView.id!;
|
||||
return {
|
||||
type: 'lnsPie',
|
||||
layers: [
|
||||
{
|
||||
indexPatternId,
|
||||
layerId,
|
||||
columns: result.columns.map(excludeMetaFromColumn),
|
||||
columnOrder: [],
|
||||
},
|
||||
],
|
||||
configuration: getConfiguration(layerId, vis, result),
|
||||
indexPatternIds: [indexPatternId],
|
||||
};
|
||||
};
|
20
src/plugins/vis_types/pie/public/convert_to_lens/types.ts
Normal file
20
src/plugins/vis_types/pie/public/convert_to_lens/types.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { PartitionVisParams } from '@kbn/expression-partition-vis-plugin/common';
|
||||
import {
|
||||
NavigateToLensContext,
|
||||
PartitionVisConfiguration,
|
||||
} from '@kbn/visualizations-plugin/common';
|
||||
import { Vis } from '@kbn/visualizations-plugin/public';
|
||||
|
||||
export type ConvertPieToLensVisualization = (
|
||||
vis: Vis<PartitionVisParams>,
|
||||
timefilter?: TimefilterContract
|
||||
) => Promise<NavigateToLensContext<PartitionVisConfiguration> | null>;
|
|
@ -6,13 +6,15 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { CoreSetup, DocLinksStart, ThemeServiceStart } from '@kbn/core/public';
|
||||
import { CoreSetup, CoreStart, DocLinksStart, ThemeServiceStart } from '@kbn/core/public';
|
||||
import { VisualizationsSetup } from '@kbn/visualizations-plugin/public';
|
||||
import { ChartsPluginSetup } from '@kbn/charts-plugin/public';
|
||||
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
|
||||
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import { LEGACY_PIE_CHARTS_LIBRARY } from '../common';
|
||||
import { pieVisType } from './vis_type';
|
||||
import { setDataViewsStart } from './services';
|
||||
|
||||
/** @internal */
|
||||
export interface VisTypePieSetupDependencies {
|
||||
|
@ -21,6 +23,11 @@ export interface VisTypePieSetupDependencies {
|
|||
usageCollection: UsageCollectionSetup;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface VisTypePieStartDependencies {
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface VisTypePiePluginStartDependencies {
|
||||
data: DataPublicPluginStart;
|
||||
|
@ -53,5 +60,7 @@ export class VisTypePiePlugin {
|
|||
return {};
|
||||
}
|
||||
|
||||
start() {}
|
||||
start(core: CoreStart, { dataViews }: VisTypePieStartDependencies) {
|
||||
setDataViewsStart(dataViews);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
import { LegendDisplay } from '@kbn/expression-partition-vis-plugin/common';
|
||||
import { LegendSize } from '@kbn/visualizations-plugin/common';
|
||||
|
||||
const mockUiStateGet = jest.fn().mockReturnValue(() => false);
|
||||
|
||||
export const samplePieVis = {
|
||||
type: {
|
||||
name: 'pie',
|
||||
|
@ -1353,5 +1355,6 @@ export const samplePieVis = {
|
|||
vis: {
|
||||
legendOpen: false,
|
||||
},
|
||||
get: mockUiStateGet,
|
||||
},
|
||||
};
|
||||
|
|
13
src/plugins/vis_types/pie/public/services.ts
Normal file
13
src/plugins/vis_types/pie/public/services.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { createGetterSetter } from '@kbn/kibana-utils-plugin/public';
|
||||
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
|
||||
export const [getDataViewsStart, setDataViewsStart] =
|
||||
createGetterSetter<DataViewsPublicPluginStart>('dataViews');
|
|
@ -21,6 +21,7 @@ import { DEFAULT_PERCENT_DECIMALS } from '../../common';
|
|||
import { PieTypeProps } from '../types';
|
||||
import { toExpressionAst } from '../to_ast';
|
||||
import { getPieOptions } from '../editor/components';
|
||||
import { convertToLens } from '../convert_to_lens';
|
||||
|
||||
export const getPieVisTypeDefinition = ({
|
||||
showElasticChartsOptions = false,
|
||||
|
@ -123,4 +124,10 @@ export const getPieVisTypeDefinition = ({
|
|||
},
|
||||
hierarchicalData: true,
|
||||
requiresSearch: true,
|
||||
navigateToLens: async (vis, timefilter) => (vis ? convertToLens(vis, timefilter) : null),
|
||||
getExpressionVariables: async (vis, timeFilter) => {
|
||||
return {
|
||||
canNavigateToLens: Boolean(vis?.params ? await convertToLens(vis, timeFilter) : null),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -36,6 +36,70 @@ export const OperationsWithReferences = {
|
|||
|
||||
export const Operations = { ...OperationsWithSourceField, ...OperationsWithReferences } as const;
|
||||
|
||||
export const PartitionChartTypes = {
|
||||
PIE: 'pie',
|
||||
DONUT: 'donut',
|
||||
TREEMAP: 'treemap',
|
||||
MOSAIC: 'mosaic',
|
||||
WAFFLE: 'waffle',
|
||||
} as const;
|
||||
|
||||
export const CategoryDisplayTypes = {
|
||||
DEFAULT: 'default',
|
||||
INSIDE: 'inside',
|
||||
HIDE: 'hide',
|
||||
} as const;
|
||||
|
||||
export const NumberDisplayTypes = {
|
||||
HIDDEN: 'hidden',
|
||||
PERCENT: 'percent',
|
||||
VALUE: 'value',
|
||||
} as const;
|
||||
|
||||
export const LegendDisplayTypes = {
|
||||
DEFAULT: 'default',
|
||||
SHOW: 'show',
|
||||
HIDE: 'hide',
|
||||
} as const;
|
||||
|
||||
export const LayerTypes = {
|
||||
DATA: 'data',
|
||||
REFERENCELINE: 'referenceLine',
|
||||
ANNOTATIONS: 'annotations',
|
||||
} as const;
|
||||
|
||||
export const XYCurveTypes = {
|
||||
LINEAR: 'LINEAR',
|
||||
CURVE_MONOTONE_X: 'CURVE_MONOTONE_X',
|
||||
CURVE_STEP_AFTER: 'CURVE_STEP_AFTER',
|
||||
} as const;
|
||||
|
||||
export const YAxisModes = {
|
||||
AUTO: 'auto',
|
||||
LEFT: 'left',
|
||||
RIGHT: 'right',
|
||||
BOTTOM: 'bottom',
|
||||
} as const;
|
||||
|
||||
export const SeriesTypes = {
|
||||
BAR: 'bar',
|
||||
LINE: 'line',
|
||||
AREA: 'area',
|
||||
BAR_STACKED: 'bar_stacked',
|
||||
AREA_STACKED: 'area_stacked',
|
||||
BAR_HORIZONTAL: 'bar_horizontal',
|
||||
BAR_PERCENTAGE_STACKED: 'bar_percentage_stacked',
|
||||
BAR_HORIZONTAL_STACKED: 'bar_horizontal_stacked',
|
||||
AREA_PERCENTAGE_STACKED: 'area_percentage_stacked',
|
||||
BAR_HORIZONTAL_PERCENTAGE_STACKED: 'bar_horizontal_percentage_stacked',
|
||||
} as const;
|
||||
|
||||
export const FillTypes = {
|
||||
NONE: 'none',
|
||||
ABOVE: 'above',
|
||||
BELOW: 'below',
|
||||
} as const;
|
||||
|
||||
export const RANGE_MODES = {
|
||||
Range: 'range',
|
||||
Histogram: 'histogram',
|
||||
|
|
|
@ -11,43 +11,27 @@ import { $Values } from '@kbn/utility-types';
|
|||
import type { CustomPaletteParams, PaletteOutput } from '@kbn/coloring';
|
||||
import { KibanaQueryOutput } from '@kbn/data-plugin/common';
|
||||
import { LegendSize } from '../../constants';
|
||||
|
||||
export const XYCurveTypes = {
|
||||
LINEAR: 'LINEAR',
|
||||
CURVE_MONOTONE_X: 'CURVE_MONOTONE_X',
|
||||
CURVE_STEP_AFTER: 'CURVE_STEP_AFTER',
|
||||
} as const;
|
||||
|
||||
export const YAxisModes = {
|
||||
AUTO: 'auto',
|
||||
LEFT: 'left',
|
||||
RIGHT: 'right',
|
||||
BOTTOM: 'bottom',
|
||||
} as const;
|
||||
|
||||
export const SeriesTypes = {
|
||||
BAR: 'bar',
|
||||
LINE: 'line',
|
||||
AREA: 'area',
|
||||
BAR_STACKED: 'bar_stacked',
|
||||
AREA_STACKED: 'area_stacked',
|
||||
BAR_HORIZONTAL: 'bar_horizontal',
|
||||
BAR_PERCENTAGE_STACKED: 'bar_percentage_stacked',
|
||||
BAR_HORIZONTAL_STACKED: 'bar_horizontal_stacked',
|
||||
AREA_PERCENTAGE_STACKED: 'area_percentage_stacked',
|
||||
BAR_HORIZONTAL_PERCENTAGE_STACKED: 'bar_horizontal_percentage_stacked',
|
||||
} as const;
|
||||
|
||||
export const FillTypes = {
|
||||
NONE: 'none',
|
||||
ABOVE: 'above',
|
||||
BELOW: 'below',
|
||||
} as const;
|
||||
import {
|
||||
CategoryDisplayTypes,
|
||||
PartitionChartTypes,
|
||||
NumberDisplayTypes,
|
||||
LegendDisplayTypes,
|
||||
FillTypes,
|
||||
SeriesTypes,
|
||||
YAxisModes,
|
||||
XYCurveTypes,
|
||||
LayerTypes,
|
||||
} from '../constants';
|
||||
|
||||
export type FillType = $Values<typeof FillTypes>;
|
||||
export type SeriesType = $Values<typeof SeriesTypes>;
|
||||
export type YAxisMode = $Values<typeof YAxisModes>;
|
||||
export type XYCurveType = $Values<typeof XYCurveTypes>;
|
||||
export type PartitionChartType = $Values<typeof PartitionChartTypes>;
|
||||
export type CategoryDisplayType = $Values<typeof CategoryDisplayTypes>;
|
||||
export type NumberDisplayType = $Values<typeof NumberDisplayTypes>;
|
||||
export type LegendDisplayType = $Values<typeof LegendDisplayTypes>;
|
||||
export type LayerType = $Values<typeof LayerTypes>;
|
||||
|
||||
export interface AxisExtentConfig {
|
||||
mode: 'full' | 'custom' | 'dataBounds';
|
||||
|
@ -217,4 +201,34 @@ export interface MetricVisConfiguration {
|
|||
maxCols?: number;
|
||||
}
|
||||
|
||||
export type Configuration = XYConfiguration | TableVisConfiguration | MetricVisConfiguration;
|
||||
export interface PartitionLayerState {
|
||||
layerId: string;
|
||||
layerType: LayerType;
|
||||
primaryGroups: string[];
|
||||
secondaryGroups?: string[];
|
||||
metric?: string;
|
||||
collapseFns?: Record<string, string>;
|
||||
numberDisplay: NumberDisplayType;
|
||||
categoryDisplay: CategoryDisplayType;
|
||||
legendDisplay: LegendDisplayType;
|
||||
legendPosition?: Position;
|
||||
showValuesInLegend?: boolean;
|
||||
nestedLegend?: boolean;
|
||||
percentDecimals?: number;
|
||||
emptySizeRatio?: number;
|
||||
legendMaxLines?: number;
|
||||
legendSize?: LegendSize;
|
||||
truncateLegend?: boolean;
|
||||
}
|
||||
|
||||
export interface PartitionVisConfiguration {
|
||||
shape: PartitionChartType;
|
||||
layers: PartitionLayerState[];
|
||||
palette?: PaletteOutput;
|
||||
}
|
||||
|
||||
export type Configuration =
|
||||
| XYConfiguration
|
||||
| TableVisConfiguration
|
||||
| PartitionVisConfiguration
|
||||
| MetricVisConfiguration;
|
||||
|
|
|
@ -24,14 +24,26 @@ import {
|
|||
sortColumns,
|
||||
} from './utils';
|
||||
|
||||
const areVisSchemasValid = (visSchemas: Schemas, unsupported: Array<keyof Schemas>) => {
|
||||
const usedUnsupportedSchemas = unsupported.filter(
|
||||
(schema) => visSchemas[schema] && visSchemas[schema]?.length
|
||||
);
|
||||
return !usedUnsupportedSchemas.length;
|
||||
};
|
||||
|
||||
export const getColumnsFromVis = <T>(
|
||||
vis: Vis<T>,
|
||||
timefilter: TimefilterContract,
|
||||
dataView: DataView,
|
||||
{ splits, buckets }: { splits: Array<keyof Schemas>; buckets: Array<keyof Schemas> } = {
|
||||
splits: [],
|
||||
buckets: [],
|
||||
},
|
||||
{
|
||||
splits = [],
|
||||
buckets = [],
|
||||
unsupported = [],
|
||||
}: {
|
||||
splits?: Array<keyof Schemas>;
|
||||
buckets?: Array<keyof Schemas>;
|
||||
unsupported?: Array<keyof Schemas>;
|
||||
} = {},
|
||||
config?: {
|
||||
dropEmptyRowsInDateHistogram?: boolean;
|
||||
}
|
||||
|
@ -41,7 +53,7 @@ export const getColumnsFromVis = <T>(
|
|||
timeRange: timefilter.getAbsoluteTime(),
|
||||
});
|
||||
|
||||
if (!isValidVis(visSchemas)) {
|
||||
if (!isValidVis(visSchemas) || !areVisSchemasValid(visSchemas, unsupported)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -111,8 +123,8 @@ export const getColumnsFromVis = <T>(
|
|||
const columnsWithoutReferenced = getColumnsWithoutReferenced(columns);
|
||||
|
||||
return {
|
||||
metrics: getColumnIds(metrics),
|
||||
buckets: getColumnIds([...bucketColumns, ...splitBucketColumns, ...customBucketColumns]),
|
||||
metrics: getColumnIds(columnsWithoutReferenced.filter((с) => !с.isBucketed)),
|
||||
buckets: getColumnIds(columnsWithoutReferenced.filter((c) => c.isBucketed)),
|
||||
bucketCollapseFn: getBucketCollapseFn(visSchemas.metric, customBucketColumns),
|
||||
columnsWithoutReferenced,
|
||||
columns,
|
||||
|
|
|
@ -96,6 +96,7 @@ const TopNav = ({
|
|||
[doReload]
|
||||
);
|
||||
|
||||
const uiStateJSON = useMemo(() => vis.uiState.toJSON(), [vis.uiState]);
|
||||
useEffect(() => {
|
||||
const asyncGetTriggerContext = async () => {
|
||||
if (vis.type.navigateToLens) {
|
||||
|
@ -107,7 +108,14 @@ const TopNav = ({
|
|||
}
|
||||
};
|
||||
asyncGetTriggerContext();
|
||||
}, [services.data.query.timefilter.timefilter, vis, vis.type, vis.params, vis.data.indexPattern]);
|
||||
}, [
|
||||
services.data.query.timefilter.timefilter,
|
||||
vis,
|
||||
vis.type,
|
||||
vis.params,
|
||||
uiStateJSON?.vis,
|
||||
vis.data.indexPattern,
|
||||
]);
|
||||
|
||||
const displayEditInLensItem = Boolean(vis.type.navigateToLens && editInLensConfig);
|
||||
const config = useMemo(() => {
|
||||
|
|
|
@ -534,12 +534,8 @@ function getTopSuggestion(
|
|||
);
|
||||
});
|
||||
|
||||
// We prefer unchanged or reduced suggestions when switching
|
||||
// charts since that allows you to switch from A to B and back
|
||||
// to A with the greatest chance of preserving your original state.
|
||||
return (
|
||||
suggestions.find((s) => s.changeType === 'unchanged') ||
|
||||
suggestions.find((s) => s.changeType === 'reduced') ||
|
||||
suggestions.find((s) => s.changeType === 'unchanged' || s.changeType === 'reduced') ||
|
||||
suggestions[0]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1774,7 +1774,7 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
state: expect.objectContaining({
|
||||
layers: {
|
||||
test: expect.objectContaining({
|
||||
columnOrder: ['column-id-3', 'column-id-2', 'column-id-1'],
|
||||
columnOrder: ['column-id-2', 'column-id-3', 'column-id-1'],
|
||||
columns: {
|
||||
'column-id-1': expect.objectContaining({
|
||||
operationType: 'count',
|
||||
|
@ -1804,10 +1804,10 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
isMultiRow: true,
|
||||
columns: [
|
||||
expect.objectContaining({
|
||||
columnId: 'column-id-3',
|
||||
columnId: 'column-id-2',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
columnId: 'column-id-2',
|
||||
columnId: 'column-id-3',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
columnId: 'column-id-1',
|
||||
|
|
|
@ -341,6 +341,7 @@ function createNewLayerWithMetricAggregationFromVizEditor(
|
|||
newLayer = insertNewColumn({
|
||||
...column,
|
||||
layer: newLayer,
|
||||
respectOrder: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -68,6 +68,7 @@ interface ColumnChange {
|
|||
columnParams?: Record<string, unknown>;
|
||||
initialParams?: { params: Record<string, unknown> }; // TODO: bind this to the op parameter
|
||||
references?: Array<Omit<ColumnChange, 'layer'>>;
|
||||
respectOrder?: boolean;
|
||||
}
|
||||
|
||||
interface ColumnCopy {
|
||||
|
@ -362,6 +363,7 @@ export function insertNewColumn({
|
|||
columnParams,
|
||||
initialParams,
|
||||
references,
|
||||
respectOrder,
|
||||
}: ColumnChange): IndexPatternLayer {
|
||||
const operationDefinition = operationDefinitionMap[op];
|
||||
|
||||
|
@ -394,7 +396,14 @@ export function insertNewColumn({
|
|||
: operationDefinition.buildColumn({ ...baseOptions, layer });
|
||||
|
||||
return updateDefaultLabels(
|
||||
addOperationFn(layer, buildColumnFn, columnId, visualizationGroups, targetGroup),
|
||||
addOperationFn(
|
||||
layer,
|
||||
buildColumnFn,
|
||||
columnId,
|
||||
visualizationGroups,
|
||||
targetGroup,
|
||||
respectOrder
|
||||
),
|
||||
indexPattern
|
||||
);
|
||||
}
|
||||
|
@ -445,7 +454,14 @@ export function insertNewColumn({
|
|||
)
|
||||
: operationDefinition.buildColumn({ ...baseOptions, layer: tempLayer, referenceIds });
|
||||
return updateDefaultLabels(
|
||||
addOperationFn(tempLayer, buildColumnFn, columnId, visualizationGroups, targetGroup),
|
||||
addOperationFn(
|
||||
tempLayer,
|
||||
buildColumnFn,
|
||||
columnId,
|
||||
visualizationGroups,
|
||||
targetGroup,
|
||||
respectOrder
|
||||
),
|
||||
indexPattern
|
||||
);
|
||||
}
|
||||
|
@ -468,7 +484,8 @@ export function insertNewColumn({
|
|||
operationDefinition.buildColumn({ ...baseOptions, layer, field: invalidField }),
|
||||
columnId,
|
||||
visualizationGroups,
|
||||
targetGroup
|
||||
targetGroup,
|
||||
respectOrder
|
||||
),
|
||||
indexPattern
|
||||
);
|
||||
|
@ -508,7 +525,7 @@ export function insertNewColumn({
|
|||
const isBucketed = Boolean(possibleOperation.isBucketed);
|
||||
const addOperationFn = isBucketed ? addBucket : addMetric;
|
||||
return updateDefaultLabels(
|
||||
addOperationFn(layer, newColumn, columnId, visualizationGroups, targetGroup),
|
||||
addOperationFn(layer, newColumn, columnId, visualizationGroups, targetGroup, respectOrder),
|
||||
indexPattern
|
||||
);
|
||||
}
|
||||
|
@ -1154,7 +1171,8 @@ function addBucket(
|
|||
column: BaseIndexPatternColumn,
|
||||
addedColumnId: string,
|
||||
visualizationGroups: VisualizationDimensionGroupConfig[],
|
||||
targetGroup?: string
|
||||
targetGroup?: string,
|
||||
respectOrder?: boolean
|
||||
): IndexPatternLayer {
|
||||
const [buckets, metrics] = partition(
|
||||
layer.columnOrder,
|
||||
|
@ -1166,7 +1184,7 @@ function addBucket(
|
|||
);
|
||||
|
||||
let updatedColumnOrder: string[] = [];
|
||||
if (oldDateHistogramIndex > -1 && column.operationType === 'terms') {
|
||||
if (oldDateHistogramIndex > -1 && column.operationType === 'terms' && !respectOrder) {
|
||||
// Insert the new terms bucket above the first date histogram
|
||||
updatedColumnOrder = [
|
||||
...buckets.slice(0, oldDateHistogramIndex),
|
||||
|
|
|
@ -35,7 +35,7 @@ export const visualizeAggBasedVisAction = (application: ApplicationStart) =>
|
|||
type: ACTION_CONVERT_TO_LENS,
|
||||
payload,
|
||||
originatingApp: i18n.translate('xpack.lens.AggBasedLabel', {
|
||||
defaultMessage: 'Aggregation based visualization',
|
||||
defaultMessage: 'aggregation based visualization',
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@ import type {
|
|||
IInterpreterRenderHandlers,
|
||||
Datatable,
|
||||
} from '@kbn/expressions-plugin/public';
|
||||
import type { NavigateToLensContext } from '@kbn/visualizations-plugin/common';
|
||||
import type { Configuration, NavigateToLensContext } from '@kbn/visualizations-plugin/common';
|
||||
import { Adapters } from '@kbn/inspector-plugin/public';
|
||||
import type { Query } from '@kbn/es-query';
|
||||
import type {
|
||||
|
@ -217,13 +217,13 @@ export interface InitializationOptions {
|
|||
isFullEditor?: boolean;
|
||||
}
|
||||
|
||||
export type VisualizeEditorContext = {
|
||||
export type VisualizeEditorContext<T extends Configuration = Configuration> = {
|
||||
savedObjectId?: string;
|
||||
embeddableId?: string;
|
||||
vizEditorOriginatingAppUrl?: string;
|
||||
originatingApp?: string;
|
||||
isVisualizeAction: boolean;
|
||||
} & NavigateToLensContext;
|
||||
} & NavigateToLensContext<T>;
|
||||
|
||||
export interface GetDropPropsArgs<T = unknown> {
|
||||
state: T;
|
||||
|
@ -1129,7 +1129,7 @@ export interface Visualization<T = unknown, P = unknown> {
|
|||
|
||||
getSuggestionFromConvertToLensContext?: (
|
||||
props: VisualizationStateFromContextChangeProps
|
||||
) => Suggestion;
|
||||
) => Suggestion | undefined;
|
||||
}
|
||||
|
||||
// Use same technique as TriggerContext
|
||||
|
|
|
@ -161,11 +161,19 @@ export const getDatatableVisualization = ({
|
|||
},
|
||||
});
|
||||
|
||||
const changeType = table.changeType;
|
||||
const changeFactor =
|
||||
changeType === 'reduced' || changeType === 'layers'
|
||||
? 0.3
|
||||
: changeType === 'unchanged'
|
||||
? 0.5
|
||||
: 1;
|
||||
|
||||
return [
|
||||
{
|
||||
title,
|
||||
// table with >= 10 columns will have a score of 0.4, fewer columns reduce score
|
||||
score: (Math.min(table.columns.length, 10) / 10) * 0.4,
|
||||
score: (Math.min(table.columns.length, 10) / 10) * 0.4 * changeFactor,
|
||||
state: {
|
||||
...(state || {}),
|
||||
layerId: table.layerId,
|
||||
|
|
|
@ -93,7 +93,7 @@ describe('suggestions', () => {
|
|||
).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should reject date operations', () => {
|
||||
it('should hide date operations', () => {
|
||||
expect(
|
||||
suggestions({
|
||||
table: {
|
||||
|
@ -118,11 +118,17 @@ describe('suggestions', () => {
|
|||
},
|
||||
state: undefined,
|
||||
keptLayerIds: ['first'],
|
||||
})
|
||||
).toHaveLength(0);
|
||||
}).map((s) => [s.hide, s.score])
|
||||
).toEqual([
|
||||
[true, 0],
|
||||
[true, 0],
|
||||
[true, 0],
|
||||
[true, 0],
|
||||
[true, 0],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should reject histogram operations', () => {
|
||||
it('should hide histogram operations', () => {
|
||||
expect(
|
||||
suggestions({
|
||||
table: {
|
||||
|
@ -147,8 +153,14 @@ describe('suggestions', () => {
|
|||
},
|
||||
state: undefined,
|
||||
keptLayerIds: ['first'],
|
||||
})
|
||||
).toHaveLength(0);
|
||||
}).map((s) => [s.hide, s.score])
|
||||
).toEqual([
|
||||
[true, 0],
|
||||
[true, 0],
|
||||
[true, 0],
|
||||
[true, 0],
|
||||
[true, 0],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not reject histogram operations in case of switching between partition charts', () => {
|
||||
|
|
|
@ -29,15 +29,10 @@ function hasIntervalScale(columns: TableSuggestionColumn[]) {
|
|||
}
|
||||
|
||||
function shouldReject({ table, keptLayerIds, state }: SuggestionRequest<PieVisualizationState>) {
|
||||
// Histograms are not good for pi. But we should not reject them on switching between partition charts.
|
||||
const shouldRejectIntervals =
|
||||
state?.shape && isPartitionShape(state.shape) ? false : hasIntervalScale(table.columns);
|
||||
|
||||
return (
|
||||
keptLayerIds.length > 1 ||
|
||||
(keptLayerIds.length && table.layerId !== keptLayerIds[0]) ||
|
||||
table.changeType === 'reorder' ||
|
||||
shouldRejectIntervals ||
|
||||
table.columns.some((col) => col.operation.isStaticValue)
|
||||
);
|
||||
}
|
||||
|
@ -111,6 +106,10 @@ export function suggestions({
|
|||
|
||||
const results: Array<VisualizationSuggestion<PieVisualizationState>> = [];
|
||||
|
||||
// Histograms are not good for pi. But we should not hide suggestion on switching between partition charts.
|
||||
const shouldHideSuggestion =
|
||||
state?.shape && isPartitionShape(state.shape) ? false : hasIntervalScale(table.columns);
|
||||
|
||||
if (
|
||||
groups.length <= PartitionChartsMeta.pie.maxBuckets &&
|
||||
!hasCustomSuggestionsExists(subVisualizationId)
|
||||
|
@ -309,11 +308,11 @@ export function suggestions({
|
|||
return [...results]
|
||||
.map((suggestion) => ({
|
||||
...suggestion,
|
||||
score: suggestion.score + 0.05 * groups.length,
|
||||
score: shouldHideSuggestion ? 0 : suggestion.score + 0.05 * groups.length,
|
||||
}))
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.map((suggestion) => ({
|
||||
...suggestion,
|
||||
hide: incompleteConfiguration || suggestion.hide,
|
||||
hide: shouldHideSuggestion || incompleteConfiguration || suggestion.hide,
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -14,11 +14,14 @@ import { ThemeServiceStart } from '@kbn/core/public';
|
|||
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { PartitionVisConfiguration } from '@kbn/visualizations-plugin/common/convert_to_lens';
|
||||
import type {
|
||||
Visualization,
|
||||
OperationMetadata,
|
||||
AccessorConfig,
|
||||
VisualizationDimensionGroupConfig,
|
||||
Suggestion,
|
||||
VisualizeEditorContext,
|
||||
} from '../../types';
|
||||
import { getSortedGroups, toExpression, toPreviewExpression } from './to_expression';
|
||||
import { CategoryDisplay, layerTypes, LegendDisplay, NumberDisplay } from '../../../common';
|
||||
|
@ -27,6 +30,17 @@ import { PartitionChartsMeta } from './partition_charts_meta';
|
|||
import { DimensionEditor, PieToolbar } from './toolbar';
|
||||
import { checkTableForContainsSmallValues } from './render_helpers';
|
||||
import { PieChartTypes, PieLayerState, PieVisualizationState } from '../../../common';
|
||||
import { IndexPatternLayer } from '../..';
|
||||
|
||||
interface DatatableDatasourceState {
|
||||
[prop: string]: unknown;
|
||||
layers: IndexPatternLayer[];
|
||||
}
|
||||
|
||||
export interface PartitionSuggestion extends Suggestion {
|
||||
datasourceState: DatatableDatasourceState;
|
||||
visualizationState: PieVisualizationState;
|
||||
}
|
||||
|
||||
function newLayerState(layerId: string): PieLayerState {
|
||||
return {
|
||||
|
@ -42,6 +56,12 @@ function newLayerState(layerId: string): PieLayerState {
|
|||
};
|
||||
}
|
||||
|
||||
function isPartitionVisConfiguration(
|
||||
context: VisualizeEditorContext
|
||||
): context is VisualizeEditorContext<PartitionVisConfiguration> {
|
||||
return context.type === 'lnsPie';
|
||||
}
|
||||
|
||||
const bucketedOperations = (op: OperationMetadata) => op.isBucketed;
|
||||
const numberMetricOperations = (op: OperationMetadata) =>
|
||||
!op.isBucketed && op.dataType === 'number' && !op.isStaticValue;
|
||||
|
@ -422,6 +442,29 @@ export const getPieVisualization = ({
|
|||
return warningMessages;
|
||||
},
|
||||
|
||||
getSuggestionFromConvertToLensContext(props) {
|
||||
const context = props.context;
|
||||
if (!isPartitionVisConfiguration(context)) {
|
||||
return;
|
||||
}
|
||||
if (!props.suggestions.length) {
|
||||
return;
|
||||
}
|
||||
const suggestionByShape = (props.suggestions as PartitionSuggestion[]).find(
|
||||
(suggestion) => suggestion.visualizationState.shape === context.configuration.shape
|
||||
);
|
||||
if (!suggestionByShape) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
...suggestionByShape,
|
||||
visualizationState: {
|
||||
...suggestionByShape.visualizationState,
|
||||
...context.configuration,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
getErrorMessages(state) {
|
||||
const hasTooManyBucketDimensions = state.layers
|
||||
.map(
|
||||
|
|
|
@ -615,7 +615,12 @@ function getScore(
|
|||
changeType: TableChangeType
|
||||
) {
|
||||
// Unchanged table suggestions half the score because the underlying data doesn't change
|
||||
const changeFactor = changeType === 'unchanged' ? 0.5 : 1;
|
||||
const changeFactor =
|
||||
changeType === 'reduced' || changeType === 'layers'
|
||||
? 0.3
|
||||
: changeType === 'unchanged'
|
||||
? 0.5
|
||||
: 1;
|
||||
// chart with multiple y values and split series will have a score of 1, single y value and no split series reduce score
|
||||
return (((yValues.length > 1 ? 2 : 1) + (splitBy ? 1 : 0)) / 3) * changeFactor;
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext
|
|||
loadTestFile(require.resolve('./error_handling'));
|
||||
loadTestFile(require.resolve('./lens_tagging'));
|
||||
loadTestFile(require.resolve('./lens_reporting'));
|
||||
loadTestFile(require.resolve('./tsvb_open_in_lens'));
|
||||
loadTestFile(require.resolve('./open_in_lens'));
|
||||
// keep these two last in the group in this order because they are messing with the default saved objects
|
||||
loadTestFile(require.resolve('./rollup'));
|
||||
loadTestFile(require.resolve('./no_data'));
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../../../../../ftr_provider_context';
|
||||
|
||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('Agg based Vis to Lens', function () {
|
||||
loadTestFile(require.resolve('./pie'));
|
||||
});
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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 } = getPageObjects([
|
||||
'visualize',
|
||||
'lens',
|
||||
'visEditor',
|
||||
'timePicker',
|
||||
'header',
|
||||
]);
|
||||
|
||||
const testSubjects = getService('testSubjects');
|
||||
const pieChart = getService('pieChart');
|
||||
|
||||
describe('Pie', function describeIndexTests() {
|
||||
const isNewChartsLibraryEnabled = true;
|
||||
|
||||
before(async () => {
|
||||
await visualize.initTests(isNewChartsLibraryEnabled);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await visualize.navigateToNewAggBasedVisualization();
|
||||
await visualize.clickPieChart();
|
||||
await visualize.clickNewSearch();
|
||||
await timePicker.setDefaultAbsoluteRange();
|
||||
});
|
||||
|
||||
it('should hide the "Edit Visualization in Lens" menu item if no split slices were defined', async () => {
|
||||
const button = await testSubjects.exists('visualizeEditInLensButton');
|
||||
expect(button).to.eql(false);
|
||||
});
|
||||
|
||||
it('should show the "Edit Visualization in Lens" menu item', async () => {
|
||||
await visEditor.clickBucket('Split slices');
|
||||
await visEditor.selectAggregation('Terms');
|
||||
await visEditor.selectField('machine.os.raw');
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
await visEditor.clickGo(isNewChartsLibraryEnabled);
|
||||
|
||||
const button = await testSubjects.exists('visualizeEditInLensButton');
|
||||
expect(button).to.eql(true);
|
||||
});
|
||||
|
||||
it('should convert to Lens', async () => {
|
||||
const expectedTableData = ['ios', 'osx', 'win 7', 'win 8', 'win xp'];
|
||||
await visEditor.clickBucket('Split slices');
|
||||
await visEditor.selectAggregation('Terms');
|
||||
await visEditor.selectField('machine.os.raw');
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
await visEditor.clickGo(isNewChartsLibraryEnabled);
|
||||
|
||||
const button = await testSubjects.find('visualizeEditInLensButton');
|
||||
await button.click();
|
||||
await lens.waitForVisualization('partitionVisChart');
|
||||
|
||||
await pieChart.expectPieChartLabels(expectedTableData, isNewChartsLibraryEnabled);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('Open in Lens', function () {
|
||||
loadTestFile(require.resolve('./tsvb'));
|
||||
loadTestFile(require.resolve('./agg_based'));
|
||||
});
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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, visualBuilder, lens, timeToVisualize, dashboard, canvas } = getPageObjects([
|
||||
'visualBuilder',
|
||||
'visualize',
|
||||
'lens',
|
||||
'timeToVisualize',
|
||||
'dashboard',
|
||||
'canvas',
|
||||
]);
|
||||
|
||||
const testSubjects = getService('testSubjects');
|
||||
const retry = getService('retry');
|
||||
const panelActions = getService('dashboardPanelActions');
|
||||
const dashboardAddPanel = getService('dashboardAddPanel');
|
||||
|
||||
describe('Dashboard to TSVB to Lens', function describeIndexTests() {
|
||||
before(async () => {
|
||||
await visualize.initTests();
|
||||
});
|
||||
|
||||
it('should convert a by value TSVB viz to a Lens viz', async () => {
|
||||
await visualize.navigateToNewVisualization();
|
||||
await visualize.clickVisualBuilder();
|
||||
await visualBuilder.checkVisualBuilderIsPresent();
|
||||
await visualBuilder.resetPage();
|
||||
await testSubjects.click('visualizeSaveButton');
|
||||
|
||||
await timeToVisualize.saveFromModal('My TSVB to Lens viz 1', {
|
||||
addToDashboard: 'new',
|
||||
saveToLibrary: false,
|
||||
});
|
||||
|
||||
await dashboard.waitForRenderComplete();
|
||||
const originalEmbeddableCount = await canvas.getEmbeddableCount();
|
||||
await panelActions.openContextMenu();
|
||||
await panelActions.clickEdit();
|
||||
|
||||
const button = await testSubjects.find('visualizeEditInLensButton');
|
||||
await button.click();
|
||||
await lens.waitForVisualization('xyVisChart');
|
||||
await retry.try(async () => {
|
||||
const dimensions = await testSubjects.findAll('lns-dimensionTrigger');
|
||||
expect(await dimensions[1].getVisibleText()).to.be('Count of records');
|
||||
});
|
||||
|
||||
await lens.saveAndReturn();
|
||||
await retry.try(async () => {
|
||||
const embeddableCount = await canvas.getEmbeddableCount();
|
||||
expect(embeddableCount).to.eql(originalEmbeddableCount);
|
||||
});
|
||||
await panelActions.removePanel();
|
||||
});
|
||||
|
||||
it('should convert a by reference TSVB viz to a Lens viz', async () => {
|
||||
await dashboardAddPanel.clickEditorMenuButton();
|
||||
await dashboardAddPanel.clickVisType('metrics');
|
||||
await testSubjects.click('visualizesaveAndReturnButton');
|
||||
// save it to library
|
||||
const originalPanel = await testSubjects.find('embeddablePanelHeading-');
|
||||
await panelActions.saveToLibrary('My TSVB to Lens viz 2', originalPanel);
|
||||
|
||||
await dashboard.waitForRenderComplete();
|
||||
const originalEmbeddableCount = await canvas.getEmbeddableCount();
|
||||
await panelActions.openContextMenu();
|
||||
await panelActions.clickEdit();
|
||||
|
||||
const button = await testSubjects.find('visualizeEditInLensButton');
|
||||
await button.click();
|
||||
await lens.waitForVisualization('legacyMtrVis');
|
||||
await retry.try(async () => {
|
||||
const dimensions = await testSubjects.findAll('lns-dimensionTrigger');
|
||||
expect(await dimensions[1].getVisibleText()).to.be('Count of records');
|
||||
});
|
||||
|
||||
await lens.saveAndReturn();
|
||||
await retry.try(async () => {
|
||||
const embeddableCount = await canvas.getEmbeddableCount();
|
||||
expect(embeddableCount).to.eql(originalEmbeddableCount);
|
||||
});
|
||||
|
||||
const panel = await testSubjects.find(`embeddablePanelHeading-`);
|
||||
const descendants = await testSubjects.findAllDescendant(
|
||||
'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION',
|
||||
panel
|
||||
);
|
||||
expect(descendants.length).to.equal(0);
|
||||
|
||||
await panelActions.removePanel();
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../../../../../ftr_provider_context';
|
||||
|
||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('TSVB to Lens', function () {
|
||||
loadTestFile(require.resolve('./metric'));
|
||||
loadTestFile(require.resolve('./timeseries'));
|
||||
loadTestFile(require.resolve('./dashboard'));
|
||||
});
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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, visualBuilder, lens } = getPageObjects(['visualBuilder', 'visualize', 'lens']);
|
||||
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
describe('Metric', function describeIndexTests() {
|
||||
before(async () => {
|
||||
await visualize.initTests();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await visualize.navigateToNewVisualization();
|
||||
await visualize.clickVisualBuilder();
|
||||
await visualBuilder.checkVisualBuilderIsPresent();
|
||||
await visualBuilder.resetPage();
|
||||
await visualBuilder.clickMetric();
|
||||
await visualBuilder.clickDataTab('metric');
|
||||
});
|
||||
|
||||
it('should show the "Edit Visualization in Lens" menu item', async () => {
|
||||
const button = await testSubjects.exists('visualizeEditInLensButton');
|
||||
expect(button).to.eql(true);
|
||||
});
|
||||
|
||||
it('should convert to Lens', async () => {
|
||||
const button = await testSubjects.find('visualizeEditInLensButton');
|
||||
await button.click();
|
||||
await lens.waitForVisualization('mtrVis');
|
||||
|
||||
const metricData = await lens.getMetricVisualizationData();
|
||||
expect(metricData[0].title).to.eql('Count of records');
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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, visualBuilder, lens, header } = getPageObjects([
|
||||
'visualBuilder',
|
||||
'visualize',
|
||||
'header',
|
||||
'lens',
|
||||
]);
|
||||
|
||||
const testSubjects = getService('testSubjects');
|
||||
const retry = getService('retry');
|
||||
const find = getService('find');
|
||||
const filterBar = getService('filterBar');
|
||||
const queryBar = getService('queryBar');
|
||||
|
||||
describe('Time Series', function describeIndexTests() {
|
||||
before(async () => {
|
||||
await visualize.initTests();
|
||||
});
|
||||
|
||||
it('should show the "Edit Visualization in Lens" menu item for a count aggregation', async () => {
|
||||
await visualize.navigateToNewVisualization();
|
||||
await visualize.clickVisualBuilder();
|
||||
await visualBuilder.checkVisualBuilderIsPresent();
|
||||
await visualBuilder.resetPage();
|
||||
const isMenuItemVisible = await find.existsByCssSelector(
|
||||
'[data-test-subj="visualizeEditInLensButton"]'
|
||||
);
|
||||
expect(isMenuItemVisible).to.be(true);
|
||||
});
|
||||
|
||||
it('visualizes field to Lens and loads fields to the dimesion editor', async () => {
|
||||
const button = await testSubjects.find('visualizeEditInLensButton');
|
||||
await button.click();
|
||||
await lens.waitForVisualization('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('@timestamp');
|
||||
expect(await dimensions[1].getVisibleText()).to.be('Count of records');
|
||||
});
|
||||
});
|
||||
|
||||
it('navigates back to TSVB when the Back button is clicked', async () => {
|
||||
const goBackBtn = await testSubjects.find('lnsApp_goBackToAppButton');
|
||||
goBackBtn.click();
|
||||
await visualBuilder.checkVisualBuilderIsPresent();
|
||||
await retry.try(async () => {
|
||||
const actualCount = await visualBuilder.getRhythmChartLegendValue();
|
||||
expect(actualCount).to.be('56');
|
||||
});
|
||||
});
|
||||
|
||||
it('should preserve app filters in lens', async () => {
|
||||
await filterBar.addFilter('extension', 'is', 'css');
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
const button = await testSubjects.find('visualizeEditInLensButton');
|
||||
await button.click();
|
||||
await lens.waitForVisualization('xyVisChart');
|
||||
|
||||
expect(await filterBar.hasFilter('extension', 'css')).to.be(true);
|
||||
});
|
||||
|
||||
it('should preserve query in lens', async () => {
|
||||
const goBackBtn = await testSubjects.find('lnsApp_goBackToAppButton');
|
||||
goBackBtn.click();
|
||||
await visualBuilder.checkVisualBuilderIsPresent();
|
||||
await queryBar.setQuery('machine.os : ios');
|
||||
await queryBar.submitQuery();
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
const button = await testSubjects.find('visualizeEditInLensButton');
|
||||
await button.click();
|
||||
await lens.waitForVisualization('xyVisChart');
|
||||
|
||||
expect(await queryBar.getQueryString()).to.equal('machine.os : ios');
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,192 +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; 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, visualBuilder, header, lens, timeToVisualize, dashboard, canvas } =
|
||||
getPageObjects([
|
||||
'visualBuilder',
|
||||
'visualize',
|
||||
'header',
|
||||
'lens',
|
||||
'timeToVisualize',
|
||||
'dashboard',
|
||||
'canvas',
|
||||
]);
|
||||
const testSubjects = getService('testSubjects');
|
||||
const find = getService('find');
|
||||
const dashboardAddPanel = getService('dashboardAddPanel');
|
||||
const panelActions = getService('dashboardPanelActions');
|
||||
const retry = getService('retry');
|
||||
const filterBar = getService('filterBar');
|
||||
const queryBar = getService('queryBar');
|
||||
|
||||
describe('TSVB to Lens', function describeIndexTests() {
|
||||
before(async () => {
|
||||
await visualize.initTests();
|
||||
});
|
||||
|
||||
describe('Time Series', () => {
|
||||
it('should show the "Edit Visualization in Lens" menu item for a count aggregation', async () => {
|
||||
await visualize.navigateToNewVisualization();
|
||||
await visualize.clickVisualBuilder();
|
||||
await visualBuilder.checkVisualBuilderIsPresent();
|
||||
await visualBuilder.resetPage();
|
||||
const isMenuItemVisible = await find.existsByCssSelector(
|
||||
'[data-test-subj="visualizeEditInLensButton"]'
|
||||
);
|
||||
expect(isMenuItemVisible).to.be(true);
|
||||
});
|
||||
|
||||
it('visualizes field to Lens and loads fields to the dimesion editor', async () => {
|
||||
const button = await testSubjects.find('visualizeEditInLensButton');
|
||||
await button.click();
|
||||
await lens.waitForVisualization('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('@timestamp');
|
||||
expect(await dimensions[1].getVisibleText()).to.be('Count of records');
|
||||
});
|
||||
});
|
||||
|
||||
it('navigates back to TSVB when the Back button is clicked', async () => {
|
||||
const goBackBtn = await testSubjects.find('lnsApp_goBackToAppButton');
|
||||
goBackBtn.click();
|
||||
await visualBuilder.checkVisualBuilderIsPresent();
|
||||
await retry.try(async () => {
|
||||
const actualCount = await visualBuilder.getRhythmChartLegendValue();
|
||||
expect(actualCount).to.be('56');
|
||||
});
|
||||
});
|
||||
|
||||
it('should preserve app filters in lens', async () => {
|
||||
await filterBar.addFilter('extension', 'is', 'css');
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
const button = await testSubjects.find('visualizeEditInLensButton');
|
||||
await button.click();
|
||||
await lens.waitForVisualization('xyVisChart');
|
||||
|
||||
expect(await filterBar.hasFilter('extension', 'css')).to.be(true);
|
||||
});
|
||||
|
||||
it('should preserve query in lens', async () => {
|
||||
const goBackBtn = await testSubjects.find('lnsApp_goBackToAppButton');
|
||||
goBackBtn.click();
|
||||
await visualBuilder.checkVisualBuilderIsPresent();
|
||||
await queryBar.setQuery('machine.os : ios');
|
||||
await queryBar.submitQuery();
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
const button = await testSubjects.find('visualizeEditInLensButton');
|
||||
await button.click();
|
||||
await lens.waitForVisualization('xyVisChart');
|
||||
|
||||
expect(await queryBar.getQueryString()).to.equal('machine.os : ios');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Metric', () => {
|
||||
beforeEach(async () => {
|
||||
await visualize.navigateToNewVisualization();
|
||||
await visualize.clickVisualBuilder();
|
||||
await visualBuilder.checkVisualBuilderIsPresent();
|
||||
await visualBuilder.resetPage();
|
||||
await visualBuilder.clickMetric();
|
||||
await visualBuilder.clickDataTab('metric');
|
||||
});
|
||||
|
||||
it('should show the "Edit Visualization in Lens" menu item', async () => {
|
||||
const button = await testSubjects.exists('visualizeEditInLensButton');
|
||||
expect(button).to.eql(true);
|
||||
});
|
||||
|
||||
it('should convert to Lens', async () => {
|
||||
const button = await testSubjects.find('visualizeEditInLensButton');
|
||||
await button.click();
|
||||
await lens.waitForVisualization('mtrVis');
|
||||
|
||||
const metricData = await lens.getMetricVisualizationData();
|
||||
expect(metricData[0].title).to.eql('Count of records');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dashboard to TSVB to Lens', () => {
|
||||
it('should convert a by value TSVB viz to a Lens viz', async () => {
|
||||
await visualize.navigateToNewVisualization();
|
||||
await visualize.clickVisualBuilder();
|
||||
await visualBuilder.checkVisualBuilderIsPresent();
|
||||
await visualBuilder.resetPage();
|
||||
await testSubjects.click('visualizeSaveButton');
|
||||
|
||||
await timeToVisualize.saveFromModal('My TSVB to Lens viz 1', {
|
||||
addToDashboard: 'new',
|
||||
saveToLibrary: false,
|
||||
});
|
||||
|
||||
await dashboard.waitForRenderComplete();
|
||||
const originalEmbeddableCount = await canvas.getEmbeddableCount();
|
||||
await panelActions.openContextMenu();
|
||||
await panelActions.clickEdit();
|
||||
|
||||
const button = await testSubjects.find('visualizeEditInLensButton');
|
||||
await button.click();
|
||||
await lens.waitForVisualization('xyVisChart');
|
||||
await retry.try(async () => {
|
||||
const dimensions = await testSubjects.findAll('lns-dimensionTrigger');
|
||||
expect(await dimensions[1].getVisibleText()).to.be('Count of records');
|
||||
});
|
||||
|
||||
await lens.saveAndReturn();
|
||||
await retry.try(async () => {
|
||||
const embeddableCount = await canvas.getEmbeddableCount();
|
||||
expect(embeddableCount).to.eql(originalEmbeddableCount);
|
||||
});
|
||||
await panelActions.removePanel();
|
||||
});
|
||||
|
||||
it('should convert a by reference TSVB viz to a Lens viz', async () => {
|
||||
await dashboardAddPanel.clickEditorMenuButton();
|
||||
await dashboardAddPanel.clickVisType('metrics');
|
||||
await testSubjects.click('visualizesaveAndReturnButton');
|
||||
// save it to library
|
||||
const originalPanel = await testSubjects.find('embeddablePanelHeading-');
|
||||
await panelActions.saveToLibrary('My TSVB to Lens viz 2', originalPanel);
|
||||
|
||||
await dashboard.waitForRenderComplete();
|
||||
const originalEmbeddableCount = await canvas.getEmbeddableCount();
|
||||
await panelActions.openContextMenu();
|
||||
await panelActions.clickEdit();
|
||||
|
||||
const button = await testSubjects.find('visualizeEditInLensButton');
|
||||
await button.click();
|
||||
await lens.waitForVisualization('legacyMtrVis');
|
||||
await retry.try(async () => {
|
||||
const dimensions = await testSubjects.findAll('lns-dimensionTrigger');
|
||||
expect(await dimensions[1].getVisibleText()).to.be('Count of records');
|
||||
});
|
||||
|
||||
await lens.saveAndReturn();
|
||||
await retry.try(async () => {
|
||||
const embeddableCount = await canvas.getEmbeddableCount();
|
||||
expect(embeddableCount).to.eql(originalEmbeddableCount);
|
||||
});
|
||||
|
||||
const panel = await testSubjects.find(`embeddablePanelHeading-`);
|
||||
const descendants = await testSubjects.findAllDescendant(
|
||||
'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION',
|
||||
panel
|
||||
);
|
||||
expect(descendants.length).to.equal(0);
|
||||
|
||||
await panelActions.removePanel();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue