mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Lens] Tsvb to lens for annotations (#140718)
* fix typo * [TSVB to Lens] tsvb part * Lens part * tests * add ignoreGlobalFilters * import corrected * adding star filled * widen the limits, spread the horizons Co-authored-by: Joe Reuter <johannes.reuter@elastic.co>
This commit is contained in:
parent
19097ddae9
commit
6fda415278
18 changed files with 430 additions and 19 deletions
|
@ -32,7 +32,7 @@ pageLoadAssetSize:
|
|||
embeddableEnhanced: 22107
|
||||
enterpriseSearch: 35741
|
||||
esUiShared: 326654
|
||||
eventAnnotation: 19500
|
||||
eventAnnotation: 20500
|
||||
expressionError: 22127
|
||||
expressionGauge: 25000
|
||||
expressionHeatmap: 27505
|
||||
|
|
|
@ -123,6 +123,7 @@ export const AvailableReferenceLineIcons = {
|
|||
MAP_MARKER: 'mapMarker',
|
||||
PIN_FILLED: 'pinFilled',
|
||||
STAR_EMPTY: 'starEmpty',
|
||||
STAR_FILLED: 'starFilled',
|
||||
TAG: 'tag',
|
||||
TRIANGLE: 'triangle',
|
||||
} as const;
|
||||
|
|
|
@ -94,6 +94,12 @@ export const iconSet = [
|
|||
value: AvailableReferenceLineIcons.STAR_EMPTY,
|
||||
label: i18n.translate('expressionXY.xyChart.iconSelect.starLabel', { defaultMessage: 'Star' }),
|
||||
},
|
||||
{
|
||||
value: AvailableReferenceLineIcons.STAR_FILLED,
|
||||
label: i18n.translate('expressionXY.xyChart.iconSelect.starFilledLabel', {
|
||||
defaultMessage: 'Star filled',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: AvailableReferenceLineIcons.TAG,
|
||||
label: i18n.translate('expressionXY.xyChart.iconSelect.tagIconLabel', {
|
||||
|
|
|
@ -19,6 +19,7 @@ export const AvailableAnnotationIcons = {
|
|||
MAP_MARKER: 'mapMarker',
|
||||
PIN_FILLED: 'pinFilled',
|
||||
STAR_EMPTY: 'starEmpty',
|
||||
STAR_FILLED: 'starFilled',
|
||||
TAG: 'tag',
|
||||
TRIANGLE: 'triangle',
|
||||
} as const;
|
||||
|
|
|
@ -33,7 +33,7 @@ const getConvertFnByType = (type: PANEL_TYPES) => {
|
|||
*/
|
||||
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)) {
|
||||
if (!model.use_kibana_indexes) {
|
||||
return null;
|
||||
}
|
||||
// Disables if model is invalid
|
||||
|
|
|
@ -17,6 +17,12 @@ import {
|
|||
} from '../../convert';
|
||||
import { getLayers } from './layers';
|
||||
import { createPanel, createSeries } from '../../__mocks__';
|
||||
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
v4: () => 'test-id',
|
||||
}));
|
||||
|
||||
describe('getLayers', () => {
|
||||
const dataSourceLayers: Record<number, Layer> = [
|
||||
|
@ -200,6 +206,84 @@ describe('getLayers', () => {
|
|||
const panelWithPercentileRankMetric = createPanel({
|
||||
series: [createSeries({ metrics: percentileRankMetrics })],
|
||||
});
|
||||
const panelWithSingleAnnotation = createPanel({
|
||||
annotations: [
|
||||
{
|
||||
fields: 'geo.src,host',
|
||||
template: 'Security Error from {{geo.src}} on {{host}}',
|
||||
query_string: {
|
||||
query: 'tags:error AND tags:security',
|
||||
language: 'lucene',
|
||||
},
|
||||
id: 'ann1',
|
||||
color: 'rgba(211,49,21,0.7)',
|
||||
time_field: 'timestamp',
|
||||
icon: 'fa-asterisk',
|
||||
ignore_global_filters: 1,
|
||||
ignore_panel_filters: 1,
|
||||
hidden: true,
|
||||
index_pattern: {
|
||||
id: 'test',
|
||||
},
|
||||
},
|
||||
],
|
||||
series: [createSeries({ metrics: staticValueMetric })],
|
||||
});
|
||||
const panelWithMultiAnnotations = createPanel({
|
||||
annotations: [
|
||||
{
|
||||
fields: 'geo.src,host',
|
||||
template: 'Security Error from {{geo.src}} on {{host}}',
|
||||
query_string: {
|
||||
query: 'tags:error AND tags:security',
|
||||
language: 'lucene',
|
||||
},
|
||||
id: 'ann1',
|
||||
color: 'rgba(211,49,21,0.7)',
|
||||
time_field: 'timestamp',
|
||||
icon: 'fa-asterisk',
|
||||
ignore_global_filters: 1,
|
||||
ignore_panel_filters: 1,
|
||||
hidden: true,
|
||||
index_pattern: {
|
||||
id: 'test',
|
||||
},
|
||||
},
|
||||
{
|
||||
query_string: {
|
||||
query: 'tags: error AND tags: security',
|
||||
language: 'kql',
|
||||
},
|
||||
id: 'ann2',
|
||||
color: 'blue',
|
||||
time_field: 'timestamp',
|
||||
icon: 'error-icon',
|
||||
ignore_global_filters: 0, // todo test ignore when PR is r
|
||||
ignore_panel_filters: 0,
|
||||
index_pattern: {
|
||||
id: 'test',
|
||||
},
|
||||
},
|
||||
{
|
||||
fields: 'category.keyword,price',
|
||||
template: 'Will be ignored',
|
||||
query_string: {
|
||||
query: 'category.keyword:*',
|
||||
language: 'kql',
|
||||
},
|
||||
id: 'ann3',
|
||||
color: 'red',
|
||||
time_field: 'order_date',
|
||||
icon: undefined,
|
||||
ignore_global_filters: 1,
|
||||
ignore_panel_filters: 1,
|
||||
index_pattern: {
|
||||
id: 'test2',
|
||||
},
|
||||
},
|
||||
],
|
||||
series: [createSeries({ metrics: staticValueMetric })],
|
||||
});
|
||||
|
||||
test.each<[string, [Record<number, Layer>, Panel], Array<Partial<XYLayerConfig>>]>([
|
||||
[
|
||||
|
@ -282,7 +366,159 @@ describe('getLayers', () => {
|
|||
},
|
||||
],
|
||||
],
|
||||
])('should return %s', (_, input, expected) => {
|
||||
expect(getLayers(...input)).toEqual(expected.map(expect.objectContaining));
|
||||
[
|
||||
'annotation layer gets correct params and converts color, extraFields and icons',
|
||||
[dataSourceLayersWithStatic, panelWithSingleAnnotation],
|
||||
[
|
||||
{
|
||||
layerType: 'referenceLine',
|
||||
accessors: ['column-id-1'],
|
||||
layerId: 'test-layer-1',
|
||||
yConfig: [
|
||||
{
|
||||
forAccessor: 'column-id-1',
|
||||
axisMode: 'right',
|
||||
color: '#68BC00',
|
||||
fill: 'below',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
layerId: 'test-id',
|
||||
layerType: 'annotations',
|
||||
ignoreGlobalFilters: true,
|
||||
annotations: [
|
||||
{
|
||||
color: '#D33115',
|
||||
extraFields: ['geo.src'],
|
||||
filter: {
|
||||
language: 'lucene',
|
||||
query: 'tags:error AND tags:security',
|
||||
type: 'kibana_query',
|
||||
},
|
||||
icon: 'asterisk',
|
||||
id: 'ann1',
|
||||
isHidden: true,
|
||||
key: {
|
||||
type: 'point_in_time',
|
||||
},
|
||||
label: 'Event',
|
||||
timeField: 'timestamp',
|
||||
type: 'query',
|
||||
},
|
||||
],
|
||||
indexPatternId: 'test',
|
||||
},
|
||||
],
|
||||
],
|
||||
[
|
||||
'multiple annotations with different data views create separate layers',
|
||||
[dataSourceLayersWithStatic, panelWithMultiAnnotations],
|
||||
[
|
||||
{
|
||||
layerType: 'referenceLine',
|
||||
accessors: ['column-id-1'],
|
||||
layerId: 'test-layer-1',
|
||||
yConfig: [
|
||||
{
|
||||
forAccessor: 'column-id-1',
|
||||
axisMode: 'right',
|
||||
color: '#68BC00',
|
||||
fill: 'below',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
layerId: 'test-id',
|
||||
layerType: 'annotations',
|
||||
ignoreGlobalFilters: true,
|
||||
annotations: [
|
||||
{
|
||||
color: '#D33115',
|
||||
extraFields: ['geo.src'],
|
||||
filter: {
|
||||
language: 'lucene',
|
||||
query: 'tags:error AND tags:security',
|
||||
type: 'kibana_query',
|
||||
},
|
||||
icon: 'asterisk',
|
||||
id: 'ann1',
|
||||
isHidden: true,
|
||||
key: {
|
||||
type: 'point_in_time',
|
||||
},
|
||||
label: 'Event',
|
||||
timeField: 'timestamp',
|
||||
type: 'query',
|
||||
},
|
||||
{
|
||||
color: '#0000FF',
|
||||
filter: {
|
||||
language: 'kql',
|
||||
query: 'tags: error AND tags: security',
|
||||
type: 'kibana_query',
|
||||
},
|
||||
icon: 'triangle',
|
||||
id: 'ann2',
|
||||
key: {
|
||||
type: 'point_in_time',
|
||||
},
|
||||
label: 'Event',
|
||||
timeField: 'timestamp',
|
||||
type: 'query',
|
||||
},
|
||||
],
|
||||
indexPatternId: 'test',
|
||||
},
|
||||
{
|
||||
layerId: 'test-id',
|
||||
layerType: 'annotations',
|
||||
ignoreGlobalFilters: true,
|
||||
annotations: [
|
||||
{
|
||||
color: '#FF0000',
|
||||
extraFields: ['category.keyword', 'price'],
|
||||
filter: {
|
||||
language: 'kql',
|
||||
query: 'category.keyword:*',
|
||||
type: 'kibana_query',
|
||||
},
|
||||
icon: 'triangle',
|
||||
id: 'ann3',
|
||||
key: {
|
||||
type: 'point_in_time',
|
||||
},
|
||||
label: 'Event',
|
||||
timeField: 'order_date',
|
||||
type: 'query',
|
||||
},
|
||||
],
|
||||
indexPatternId: 'test2',
|
||||
},
|
||||
],
|
||||
],
|
||||
])('should return %s', async (_, input, expected) => {
|
||||
const layers = await getLayers(...input, indexPatternsService as DataViewsPublicPluginStart);
|
||||
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;
|
||||
|
|
|
@ -7,13 +7,23 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
EventAnnotationConfig,
|
||||
FillTypes,
|
||||
XYAnnotationsLayerConfig,
|
||||
XYLayerConfig,
|
||||
YAxisMode,
|
||||
} from '@kbn/visualizations-plugin/common/convert_to_lens';
|
||||
import { PaletteOutput } from '@kbn/coloring';
|
||||
import { v4 } from 'uuid';
|
||||
import { transparentize } from '@elastic/eui';
|
||||
import Color from 'color';
|
||||
import { euiLightVars } from '@kbn/ui-theme';
|
||||
import { groupBy } from 'lodash';
|
||||
import { DataViewsPublicPluginStart, DataView } from '@kbn/data-plugin/public/data_views';
|
||||
import { fetchIndexPattern } from '../../../../../common/index_patterns_utils';
|
||||
import { ICON_TYPES_MAP } from '../../../../application/visualizations/constants';
|
||||
import { SUPPORTED_METRICS } from '../../metrics';
|
||||
import type { Metric, Panel } from '../../../../../common/types';
|
||||
import type { Annotation, Metric, Panel } from '../../../../../common/types';
|
||||
import { getSeriesAgg } from '../../series';
|
||||
import {
|
||||
isPercentileRanksColumnWithMeta,
|
||||
|
@ -51,11 +61,16 @@ function getColor(
|
|||
return seriesColor;
|
||||
}
|
||||
|
||||
export const getLayers = (
|
||||
function nonNullable<T>(value: T): value is NonNullable<T> {
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
|
||||
export const getLayers = async (
|
||||
dataSourceLayers: Record<number, Layer>,
|
||||
model: Panel
|
||||
): XYLayerConfig[] => {
|
||||
return Object.keys(dataSourceLayers).map((key) => {
|
||||
model: Panel,
|
||||
dataViews: DataViewsPublicPluginStart
|
||||
): Promise<XYLayerConfig[]> => {
|
||||
const nonAnnotationsLayers: XYLayerConfig[] = Object.keys(dataSourceLayers).map((key) => {
|
||||
const series = model.series[parseInt(key, 10)];
|
||||
const { metrics, seriesAgg } = getSeriesAgg(series.metrics);
|
||||
const dataSourceLayer = dataSourceLayers[parseInt(key, 10)];
|
||||
|
@ -112,4 +127,77 @@ export const getLayers = (
|
|||
};
|
||||
}
|
||||
});
|
||||
if (!model.annotations || !model.annotations.length) {
|
||||
return nonAnnotationsLayers;
|
||||
}
|
||||
|
||||
const annotationsByIndexPattern = groupBy(
|
||||
model.annotations,
|
||||
(a) => typeof a.index_pattern === 'object' && 'id' in a.index_pattern && a.index_pattern.id
|
||||
);
|
||||
|
||||
const annotationsLayers: Array<XYAnnotationsLayerConfig | undefined> = await Promise.all(
|
||||
Object.entries(annotationsByIndexPattern).map(async ([indexPatternId, annotations]) => {
|
||||
const convertedAnnotations: EventAnnotationConfig[] = [];
|
||||
const { indexPattern } = (await fetchIndexPattern({ id: indexPatternId }, dataViews)) || {};
|
||||
|
||||
if (indexPattern) {
|
||||
annotations.forEach((a: Annotation) => {
|
||||
const lensAnnotation = convertAnnotation(a, indexPattern);
|
||||
if (lensAnnotation) {
|
||||
convertedAnnotations.push(lensAnnotation);
|
||||
}
|
||||
});
|
||||
return {
|
||||
layerId: v4(),
|
||||
layerType: 'annotations',
|
||||
ignoreGlobalFilters: true,
|
||||
annotations: convertedAnnotations,
|
||||
indexPatternId,
|
||||
};
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return nonAnnotationsLayers.concat(...annotationsLayers.filter(nonNullable));
|
||||
};
|
||||
|
||||
const convertAnnotation = (
|
||||
annotation: Annotation,
|
||||
dataView: DataView
|
||||
): EventAnnotationConfig | undefined => {
|
||||
if (annotation.query_string) {
|
||||
const extraFields = annotation.fields
|
||||
? annotation.fields
|
||||
?.replace(/\s/g, '')
|
||||
?.split(',')
|
||||
.map((field) => {
|
||||
const dataViewField = dataView.getFieldByName(field);
|
||||
return dataViewField && dataViewField.aggregatable ? field : undefined;
|
||||
})
|
||||
.filter(nonNullable)
|
||||
: undefined;
|
||||
return {
|
||||
type: 'query',
|
||||
id: annotation.id,
|
||||
label: 'Event',
|
||||
key: {
|
||||
type: 'point_in_time',
|
||||
},
|
||||
color: new Color(transparentize(annotation.color || euiLightVars.euiColorAccent, 1)).hex(),
|
||||
timeField: annotation.time_field,
|
||||
icon:
|
||||
annotation.icon &&
|
||||
ICON_TYPES_MAP[annotation.icon] &&
|
||||
typeof ICON_TYPES_MAP[annotation.icon] === 'string'
|
||||
? ICON_TYPES_MAP[annotation.icon]
|
||||
: 'triangle',
|
||||
filter: {
|
||||
type: 'kibana_query',
|
||||
...annotation.query_string,
|
||||
},
|
||||
extraFields,
|
||||
isHidden: annotation.hidden,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import { parseTimeShift } from '@kbn/data-plugin/common';
|
||||
import { Layer } from '@kbn/visualizations-plugin/common/convert_to_lens';
|
||||
import uuid from 'uuid';
|
||||
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import { Panel } from '../../../common/types';
|
||||
import { PANEL_TYPES } from '../../../common/enums';
|
||||
import { getDataViewsStart } from '../../services';
|
||||
|
@ -37,7 +38,7 @@ const excludeMetaFromLayers = (layers: Record<string, ExtendedLayer>): Record<st
|
|||
};
|
||||
|
||||
export const convertToLens: ConvertTsvbToLensVisualization = async (model: Panel) => {
|
||||
const dataViews = getDataViewsStart();
|
||||
const dataViews: DataViewsPublicPluginStart = getDataViewsStart();
|
||||
const extendedLayers: Record<number, ExtendedLayer> = {};
|
||||
const seriesNum = model.series.filter((series) => !series.hidden).length;
|
||||
|
||||
|
@ -96,9 +97,11 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model: Panel
|
|||
};
|
||||
}
|
||||
|
||||
const configLayers = await getLayers(extendedLayers, model, dataViews);
|
||||
|
||||
return {
|
||||
type: 'lnsXY',
|
||||
layers: Object.values(excludeMetaFromLayers(extendedLayers)),
|
||||
configuration: getConfiguration(model, getLayers(extendedLayers, model)),
|
||||
configuration: getConfiguration(model, configLayers),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -79,9 +79,11 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model, timeR
|
|||
};
|
||||
}
|
||||
|
||||
const configLayers = await getLayers(extendedLayers, model, dataViews);
|
||||
|
||||
return {
|
||||
type: 'lnsXY',
|
||||
layers: Object.values(excludeMetaFromLayers(extendedLayers)),
|
||||
configuration: getConfiguration(model, getLayers(extendedLayers, model)),
|
||||
configuration: getConfiguration(model, configLayers),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -8,3 +8,4 @@
|
|||
|
||||
export * from './types';
|
||||
export * from './constants';
|
||||
export * from './utils';
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import { HorizontalAlignment, Position, VerticalAlignment } from '@elastic/charts';
|
||||
import { $Values } from '@kbn/utility-types';
|
||||
import type { PaletteOutput } from '@kbn/coloring';
|
||||
import { KibanaQueryOutput } from '@kbn/data-plugin/common';
|
||||
import { LegendSize } from '../../constants';
|
||||
|
||||
export const XYCurveTypes = {
|
||||
|
@ -90,7 +91,33 @@ export interface XYReferenceLineLayerConfig {
|
|||
layerType: 'referenceLine';
|
||||
}
|
||||
|
||||
export type XYLayerConfig = XYDataLayerConfig | XYReferenceLineLayerConfig;
|
||||
export interface EventAnnotationConfig {
|
||||
id: string;
|
||||
filter: KibanaQueryOutput;
|
||||
timeField?: string;
|
||||
extraFields?: string[];
|
||||
label: string;
|
||||
color?: string;
|
||||
isHidden?: boolean;
|
||||
icon?: string;
|
||||
type: 'query';
|
||||
key: {
|
||||
type: 'point_in_time';
|
||||
};
|
||||
}
|
||||
|
||||
export interface XYAnnotationsLayerConfig {
|
||||
layerId: string;
|
||||
annotations: EventAnnotationConfig[];
|
||||
ignoreGlobalFilters: boolean;
|
||||
layerType: 'annotations';
|
||||
indexPatternId: string;
|
||||
}
|
||||
|
||||
export type XYLayerConfig =
|
||||
| XYDataLayerConfig
|
||||
| XYReferenceLineLayerConfig
|
||||
| XYAnnotationsLayerConfig;
|
||||
|
||||
export interface AxesSettingsConfig {
|
||||
x: boolean;
|
||||
|
|
13
src/plugins/visualizations/common/convert_to_lens/utils.ts
Normal file
13
src/plugins/visualizations/common/convert_to_lens/utils.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 { XYAnnotationsLayerConfig, XYLayerConfig } from './types';
|
||||
|
||||
export const isAnnotationsLayer = (
|
||||
layer: Pick<XYLayerConfig, 'layerType'>
|
||||
): layer is XYAnnotationsLayerConfig => layer.layerType === 'annotations';
|
|
@ -203,7 +203,7 @@ export async function mountApp(
|
|||
});
|
||||
}
|
||||
};
|
||||
// get state from location, used for nanigating from Visualize/Discover to Lens
|
||||
// get state from location, used for navigating from Visualize/Discover to Lens
|
||||
const initialContext =
|
||||
historyLocationState &&
|
||||
(historyLocationState.type === ACTION_VISUALIZE_LENS_FIELD ||
|
||||
|
|
|
@ -13,6 +13,7 @@ import { difference } from 'lodash';
|
|||
import type { DataViewsContract, DataViewSpec } from '@kbn/data-views-plugin/public';
|
||||
import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
|
||||
import { DataViewPersistableStateService } from '@kbn/data-views-plugin/common';
|
||||
import { isAnnotationsLayer } from '@kbn/visualizations-plugin/common/convert_to_lens';
|
||||
import {
|
||||
Datasource,
|
||||
DatasourceLayers,
|
||||
|
@ -53,6 +54,9 @@ function getIndexPatterns(
|
|||
for (const { indexPatternId } of initialContext.layers) {
|
||||
indexPatternIds.push(indexPatternId);
|
||||
}
|
||||
for (const l of initialContext.configuration.layers) {
|
||||
if (isAnnotationsLayer(l)) indexPatternIds.push(l.indexPatternId);
|
||||
}
|
||||
} else {
|
||||
indexPatternIds.push(initialContext.dataViewSpec.id!);
|
||||
}
|
||||
|
@ -209,6 +213,7 @@ export async function initializeSources(
|
|||
visualizationMap,
|
||||
visualizationState,
|
||||
references,
|
||||
initialContext,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
@ -217,16 +222,19 @@ export function initializeVisualization({
|
|||
visualizationMap,
|
||||
visualizationState,
|
||||
references,
|
||||
initialContext,
|
||||
}: {
|
||||
visualizationState: VisualizationState;
|
||||
visualizationMap: VisualizationMap;
|
||||
references?: SavedObjectReference[];
|
||||
initialContext?: VisualizeFieldContext | VisualizeEditorContext;
|
||||
}) {
|
||||
if (visualizationState?.activeId) {
|
||||
return (
|
||||
visualizationMap[visualizationState.activeId]?.fromPersistableState?.(
|
||||
visualizationState.state,
|
||||
references
|
||||
references,
|
||||
initialContext
|
||||
) ?? visualizationState.state
|
||||
);
|
||||
}
|
||||
|
|
|
@ -917,7 +917,11 @@ export interface Visualization<T = unknown, P = unknown> {
|
|||
/** Visualizations can have references as well */
|
||||
getPersistableState?: (state: T) => { state: P; savedObjectReferences: SavedObjectReference[] };
|
||||
/** Hydrate from persistable state and references to final state */
|
||||
fromPersistableState?: (state: P, references?: SavedObjectReference[]) => T;
|
||||
fromPersistableState?: (
|
||||
state: P,
|
||||
references?: SavedObjectReference[],
|
||||
initialContext?: VisualizeFieldContext | VisualizeEditorContext
|
||||
) => T;
|
||||
/** Frame needs to know which layers the visualization is currently using */
|
||||
getLayerIds: (state: T) => string[];
|
||||
/** Reset button on each layer triggers this */
|
||||
|
|
|
@ -10,11 +10,13 @@ import { EuiIconType } from '@elastic/eui/src/components/icon/icon';
|
|||
import type { SavedObjectReference } from '@kbn/core/public';
|
||||
import { isQueryAnnotationConfig } from '@kbn/event-annotation-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public';
|
||||
import { validateQuery } from '../../shared_components';
|
||||
import type {
|
||||
FramePublicAPI,
|
||||
DatasourcePublicAPI,
|
||||
VisualizationDimensionGroupConfig,
|
||||
VisualizeEditorContext,
|
||||
} from '../../types';
|
||||
import {
|
||||
visualizationTypes,
|
||||
|
@ -26,6 +28,7 @@ import {
|
|||
XYState,
|
||||
XYPersistedState,
|
||||
State,
|
||||
XYAnnotationLayerConfig,
|
||||
} from './types';
|
||||
import { getDataLayers, isAnnotationsLayer, isDataLayer } from './visualization_helpers';
|
||||
|
||||
|
@ -140,11 +143,13 @@ export function extractReferences(state: XYState) {
|
|||
|
||||
export function injectReferences(
|
||||
state: XYPersistedState,
|
||||
references?: SavedObjectReference[]
|
||||
references?: SavedObjectReference[],
|
||||
initialContext?: VisualizeFieldContext | VisualizeEditorContext
|
||||
): XYState {
|
||||
if (!references || !references.length) {
|
||||
return state as XYState;
|
||||
}
|
||||
|
||||
const fallbackIndexPatternId = references.find(({ type }) => type === 'index-pattern')!.id;
|
||||
return {
|
||||
...state,
|
||||
|
@ -155,6 +160,7 @@ export function injectReferences(
|
|||
return {
|
||||
...layer,
|
||||
indexPatternId:
|
||||
getIndexPatternIdFromInitialContext(layer, initialContext) ||
|
||||
references.find(({ name }) => name === getLayerReferenceName(layer.layerId))?.id ||
|
||||
fallbackIndexPatternId,
|
||||
};
|
||||
|
@ -162,6 +168,15 @@ export function injectReferences(
|
|||
};
|
||||
}
|
||||
|
||||
function getIndexPatternIdFromInitialContext(
|
||||
layer: XYAnnotationLayerConfig,
|
||||
initialContext?: VisualizeFieldContext | VisualizeEditorContext
|
||||
) {
|
||||
if (initialContext && 'isVisualizeAction' in initialContext) {
|
||||
return layer && 'indexPatternId' in layer ? layer.indexPatternId : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function validateColumn(
|
||||
state: State,
|
||||
frame: Pick<FramePublicAPI, 'dataViews'>,
|
||||
|
|
|
@ -184,8 +184,8 @@ export const getXyVisualization = ({
|
|||
return extractReferences(state);
|
||||
},
|
||||
|
||||
fromPersistableState(state, references) {
|
||||
return injectReferences(state, references);
|
||||
fromPersistableState(state, references, initialContext) {
|
||||
return injectReferences(state, references, initialContext);
|
||||
},
|
||||
|
||||
getDescription,
|
||||
|
|
|
@ -82,6 +82,12 @@ export const annotationsIconSet: IconSet<AvailableAnnotationIcon> = [
|
|||
value: 'starEmpty',
|
||||
label: i18n.translate('xpack.lens.xyChart.iconSelect.starLabel', { defaultMessage: 'Star' }),
|
||||
},
|
||||
{
|
||||
value: 'starFilled',
|
||||
label: i18n.translate('xpack.lens.xyChart.iconSelect.starFilledLabel', {
|
||||
defaultMessage: 'Star filled',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'tag',
|
||||
label: i18n.translate('xpack.lens.xyChart.iconSelect.tagIconLabel', {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue