diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.test.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.test.ts new file mode 100644 index 0000000000000..814a5996af638 --- /dev/null +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.test.ts @@ -0,0 +1,241 @@ +/* + * 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 { EmbeddableStateWithType } from 'src/plugins/embeddable/common'; +import { VisualizeEmbeddableFactory, VisualizeInput } from '.'; +import { VisualizeEmbeddableFactoryDeps } from './visualize_embeddable_factory'; + +describe('visualize_embeddable_factory', () => { + const factory = new VisualizeEmbeddableFactory({} as VisualizeEmbeddableFactoryDeps); + test('extract saved search references for search source state and not store them in state', () => { + const { state, references } = factory.extract({ + savedVis: { + type: 'area', + params: {}, + uiState: {}, + data: { + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + params: {}, + schema: 'metric', + }, + ], + searchSource: { + query: { + query: '', + language: 'kuery', + }, + filter: [], + }, + savedSearchId: '123', + }, + }, + enhancements: {}, + type: 'visualization', + } as unknown as EmbeddableStateWithType); + expect(references).toEqual([ + { + type: 'search', + name: 'search_0', + id: '123', + }, + ]); + expect((state as unknown as VisualizeInput).savedVis?.data.savedSearchId).toBeUndefined(); + }); + + test('extract data view references for search source state and not store them in state', () => { + const { state, references } = factory.extract({ + savedVis: { + type: 'area', + params: {}, + uiState: {}, + data: { + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + params: {}, + schema: 'metric', + }, + ], + searchSource: { + query: { + query: '', + language: 'kuery', + }, + index: '123', + filter: [], + }, + }, + }, + enhancements: {}, + type: 'visualization', + } as unknown as EmbeddableStateWithType); + expect(references).toEqual([ + { + type: 'index-pattern', + name: ( + (state as unknown as VisualizeInput).savedVis?.data.searchSource as { + indexRefName: string; + } + ).indexRefName, + id: '123', + }, + ]); + expect((state as unknown as VisualizeInput).savedVis?.data.searchSource.index).toBeUndefined(); + }); + + test('inject data view references into search source state', () => { + const embeddedState = factory.inject( + { + savedVis: { + type: 'area', + params: {}, + uiState: {}, + data: { + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + params: {}, + schema: 'metric', + }, + ], + searchSource: { + query: { + query: '', + language: 'kuery', + }, + indexRefName: 'x', + filter: [], + }, + }, + }, + enhancements: {}, + type: 'visualization', + } as unknown as EmbeddableStateWithType, + [{ name: 'x', id: '123', type: 'index-pattern' }] + ) as VisualizeInput; + expect(embeddedState.savedVis!.data.searchSource.index).toBe('123'); + expect( + (embeddedState.savedVis!.data.searchSource as { indexRefName: string }).indexRefName + ).toBe(undefined); + }); + + test('inject data view reference into search source state even if it is in injected state already', () => { + const embeddedState = factory.inject( + { + savedVis: { + type: 'area', + params: {}, + uiState: {}, + data: { + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + params: {}, + schema: 'metric', + }, + ], + searchSource: { + query: { + query: '', + language: 'kuery', + }, + index: '456', + filter: [], + }, + }, + }, + enhancements: {}, + type: 'visualization', + } as unknown as EmbeddableStateWithType, + [{ name: 'kibanaSavedObjectMeta.searchSourceJSON.index', id: '123', type: 'index-pattern' }] + ) as VisualizeInput; + expect(embeddedState.savedVis!.data.searchSource.index).toBe('123'); + expect( + (embeddedState.savedVis!.data.searchSource as { indexRefName: string }).indexRefName + ).toBe(undefined); + }); + + test('inject search reference into search source state', () => { + const embeddedState = factory.inject( + { + savedVis: { + type: 'area', + params: {}, + uiState: {}, + data: { + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + params: {}, + schema: 'metric', + }, + ], + searchSource: { + query: { + query: '', + language: 'kuery', + }, + filter: [], + }, + }, + }, + enhancements: {}, + type: 'visualization', + } as unknown as EmbeddableStateWithType, + [{ name: 'search_0', id: '123', type: 'search' }] + ); + expect((embeddedState as VisualizeInput).savedVis!.data.savedSearchId).toBe('123'); + }); + + test('inject search reference into search source state even if it is injected already', () => { + const embeddedState = factory.inject( + { + savedVis: { + type: 'area', + params: {}, + uiState: {}, + data: { + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + params: {}, + schema: 'metric', + }, + ], + searchSource: { + query: { + query: '', + language: 'kuery', + }, + filter: [], + }, + savedSearchId: '789', + }, + }, + enhancements: {}, + type: 'visualization', + } as unknown as EmbeddableStateWithType, + [{ name: 'search_0', id: '123', type: 'search' }] + ); + expect((embeddedState as VisualizeInput).savedVis!.data.savedSearchId).toBe('123'); + }); +}); diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx index 22c39ad1d596a..2a7663082d152 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx @@ -11,7 +11,11 @@ import { first } from 'rxjs/operators'; import type { SavedObjectMetaData, OnSaveProps } from 'src/plugins/saved_objects/public'; import type { EmbeddableStateWithType } from 'src/plugins/embeddable/common'; -import { extractSearchSourceReferences } from '../../../data/public'; +import { + injectSearchSourceReferences, + extractSearchSourceReferences, + SerializedSearchSourceFields, +} from '../../../data/public'; import type { SavedObjectAttributes, SavedObjectReference } from '../../../../core/public'; import { @@ -284,7 +288,7 @@ export class VisualizeEmbeddableFactory } public inject(_state: EmbeddableStateWithType, references: SavedObjectReference[]) { - const state = _state as unknown as VisualizeInput; + let state = _state as unknown as VisualizeInput; const { type, params } = state.savedVis ?? {}; @@ -293,21 +297,40 @@ export class VisualizeEmbeddableFactory injectTimeSeriesReferences(type, params, references); } - return _state; + if (state.savedVis?.data.searchSource) { + let extractedSearchSource = state.savedVis?.data + .searchSource as SerializedSearchSourceFields & { + indexRefName: string; + }; + if (!('indexRefName' in state.savedVis.data.searchSource)) { + // due to a bug in 8.0, some visualizations were saved with an injected state - re-extract in that case and inject the upstream references because they might have changed + extractedSearchSource = extractSearchSourceReferences( + extractedSearchSource + )[0] as SerializedSearchSourceFields & { + indexRefName: string; + }; + } + const injectedSearchSource = injectSearchSourceReferences(extractedSearchSource, references); + state = { + ...state, + savedVis: { + ...state.savedVis, + data: { + ...state.savedVis.data, + searchSource: injectedSearchSource, + savedSearchId: references.find((r) => r.name === 'search_0')?.id, + }, + }, + }; + } + + return state as EmbeddableStateWithType; } public extract(_state: EmbeddableStateWithType) { - const state = _state as unknown as VisualizeInput; + let state = _state as unknown as VisualizeInput; const references = []; - if (state.savedVis?.data.searchSource) { - const [, searchSourceReferences] = extractSearchSourceReferences( - state.savedVis.data.searchSource - ); - - references.push(...searchSourceReferences); - } - if (state.savedVis?.data.savedSearchId) { references.push({ name: 'search_0', @@ -316,6 +339,25 @@ export class VisualizeEmbeddableFactory }); } + if (state.savedVis?.data.searchSource) { + const [extractedSearchSource, searchSourceReferences] = extractSearchSourceReferences( + state.savedVis.data.searchSource + ); + + references.push(...searchSourceReferences); + state = { + ...state, + savedVis: { + ...state.savedVis, + data: { + ...state.savedVis.data, + searchSource: extractedSearchSource, + savedSearchId: undefined, + }, + }, + }; + } + const { type, params } = state.savedVis ?? {}; if (type && params) { @@ -323,6 +365,6 @@ export class VisualizeEmbeddableFactory extractTimeSeriesReferences(type, params, references, `metrics_${state.id}`); } - return { state: _state, references }; + return { state: state as EmbeddableStateWithType, references }; } }