diff --git a/x-pack/plugins/lens/public/react_embeddable/data_loader.ts b/x-pack/plugins/lens/public/react_embeddable/data_loader.ts index ac69b79b42230..a1d2e713d3f81 100644 --- a/x-pack/plugins/lens/public/react_embeddable/data_loader.ts +++ b/x-pack/plugins/lens/public/react_embeddable/data_loader.ts @@ -22,13 +22,7 @@ import { import fastIsEqual from 'fast-deep-equal'; import { pick } from 'lodash'; import { getEditPath } from '../../common/constants'; -import type { - GetStateType, - LensApi, - LensInternalApi, - LensPublicCallbacks, - VisualizationContextHelper, -} from './types'; +import type { GetStateType, LensApi, LensInternalApi, LensPublicCallbacks } from './types'; import { getExpressionRendererParams } from './expressions/expression_params'; import type { LensEmbeddableStartServices } from './types'; import { prepareCallbacks } from './expressions/callbacks'; @@ -84,7 +78,6 @@ export function loadEmbeddableData( parentApi: unknown, internalApi: LensInternalApi, services: LensEmbeddableStartServices, - { getVisualizationContext, updateVisualizationContext }: VisualizationContextHelper, metaInfo?: SharingSavedObjectProps ) { const { onLoad, onBeforeBadgesRender, ...callbacks } = apiHasLensComponentCallbacks(parentApi) @@ -103,7 +96,6 @@ export function loadEmbeddableData( } = buildUserMessagesHelpers( api, internalApi, - getVisualizationContext, services, onBeforeBadgesRender, services.spaces, @@ -174,7 +166,7 @@ export function loadEmbeddableData( }; const onDataCallback = (adapters: Partial | undefined) => { - updateVisualizationContext({ + internalApi.updateVisualizationContext({ activeData: adapters?.tables?.tables, }); // data has loaded @@ -243,8 +235,8 @@ export function loadEmbeddableData( // update the visualization context before anything else // as it will be used to compute blocking errors also in case of issues - updateVisualizationContext({ - doc: currentState.attributes, + internalApi.updateVisualizationContext({ + activeAttributes: currentState.attributes, mergedSearchContext: params?.searchContext || {}, ...rest, }); diff --git a/x-pack/plugins/lens/public/react_embeddable/helper.ts b/x-pack/plugins/lens/public/react_embeddable/helper.ts index 3ee63d907068d..bc0b7c957b9d4 100644 --- a/x-pack/plugins/lens/public/react_embeddable/helper.ts +++ b/x-pack/plugins/lens/public/react_embeddable/helper.ts @@ -40,7 +40,7 @@ export function createEmptyLensState( query: query || { query: '', language: 'kuery' }, filters: filters || [], internalReferences: [], - datasourceStates: { ...(isTextBased ? { text_based: {} } : { form_based: {} }) }, + datasourceStates: { ...(isTextBased ? { textBased: {} } : { formBased: {} }) }, visualization: {}, }, }, diff --git a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_actions.test.ts b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_actions.test.ts index a4f84c329bd3c..f669a7091053f 100644 --- a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_actions.test.ts +++ b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_actions.test.ts @@ -13,8 +13,8 @@ import { getLensApiMock, makeEmbeddableServices, getLensRuntimeStateMock, - getVisualizationContextHelperMock, createUnifiedSearchApi, + getLensInternalApiMock, } from '../mocks'; import { createEmptyLensState } from '../helper'; const DATAVIEW_ID = 'myDataView'; @@ -40,11 +40,17 @@ function setupActionsApi( ) { const services = makeEmbeddableServices(undefined, undefined, { visOverrides: { id: 'lnsXY' }, - dataOverrides: { id: 'form_based' }, + dataOverrides: { id: 'formBased' }, }); const uuid = faker.random.uuid(); const runtimeState = getLensRuntimeStateMock(stateOverrides); const apiMock = getLensApiMock(); + // create the internal API and customize internal state + const internalApi = getLensInternalApiMock(); + internalApi.updateVisualizationContext({ + ...contextOverrides, + activeAttributes: runtimeState.attributes, + }); const { api } = initializeActionApi( uuid, @@ -53,7 +59,7 @@ function setupActionsApi( createUnifiedSearchApi(), pick(apiMock, ['timeRange$']), pick(apiMock, ['panelTitle']), - getVisualizationContextHelperMock(stateOverrides?.attributes, contextOverrides), + internalApi, { ...services, data: { @@ -85,6 +91,7 @@ describe('Dashboard actions', () => { describe('Explore in Discover', () => { // make it pass the basic check on viewUnderlyingData const visualizationContextMockOverrides = { + activeAttributes: undefined, mergedSearchContext: {}, indexPatterns: { [DATAVIEW_ID]: { diff --git a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_actions.ts b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_actions.ts index 65fd13c8fca50..ac6739d9ded1c 100644 --- a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_actions.ts +++ b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_actions.ts @@ -34,10 +34,10 @@ import { buildObservableVariable, isTextBasedLanguage } from '../helper'; import type { GetStateType, LensEmbeddableStartServices, + LensInternalApi, LensRuntimeState, ViewInDiscoverCallbacks, ViewUnderlyingDataArgs, - VisualizationContextHelper, } from '../types'; import { getActiveDatasourceIdFromDoc, getActiveVisualizationIdFromDoc } from '../../utils'; @@ -120,7 +120,7 @@ function getViewUnderlyingDataArgs({ function loadViewUnderlyingDataArgs( state: LensRuntimeState, - { getVisualizationContext }: VisualizationContextHelper, + { getVisualizationContext }: LensInternalApi, searchContextApi: { timeRange$: PublishingSubject }, parentApi: unknown, { @@ -132,16 +132,21 @@ function loadViewUnderlyingDataArgs( visualizationMap, }: LensEmbeddableStartServices ) { - const { doc, activeData, activeDatasourceState, activeVisualizationState, indexPatterns } = - getVisualizationContext(); - const activeVisualizationId = getActiveVisualizationIdFromDoc(doc); - const activeDatasourceId = getActiveDatasourceIdFromDoc(doc); + const { + activeAttributes, + activeData, + activeDatasourceState, + activeVisualizationState, + indexPatterns, + } = getVisualizationContext(); + const activeVisualizationId = getActiveVisualizationIdFromDoc(activeAttributes); + const activeDatasourceId = getActiveDatasourceIdFromDoc(activeAttributes); const activeVisualization = activeVisualizationId ? visualizationMap[activeVisualizationId] : undefined; const activeDatasource = activeDatasourceId ? datasourceMap[activeDatasourceId] : undefined; if ( - !doc || + !activeAttributes || !activeData || !activeDatasource || !activeDatasourceState || @@ -199,7 +204,7 @@ function loadViewUnderlyingDataArgs( function createViewUnderlyingDataApis( getState: GetStateType, - visualizationContextHelper: VisualizationContextHelper, + internalApi: LensInternalApi, searchContextApi: { timeRange$: PublishingSubject }, parentApi: unknown, services: LensEmbeddableStartServices @@ -213,7 +218,7 @@ function createViewUnderlyingDataApis( loadViewUnderlyingData: () => { viewUnderlyingDataArgs = loadViewUnderlyingDataArgs( getState(), - visualizationContextHelper, + internalApi, searchContextApi, parentApi, services @@ -237,7 +242,7 @@ export function initializeActionApi( parentApi: unknown, searchContextApi: { timeRange$: PublishingSubject }, titleApi: { panelTitle: PublishingSubject }, - visualizationContextHelper: VisualizationContextHelper, + internalApi: LensInternalApi, services: LensEmbeddableStartServices ): { api: ViewInDiscoverCallbacks & HasDynamicActions; @@ -257,7 +262,7 @@ export function initializeActionApi( ...(isTextBasedLanguage(initialState) ? {} : dynamicActionsApi?.dynamicActionsApi ?? {}), ...createViewUnderlyingDataApis( getLatestState, - visualizationContextHelper, + internalApi, searchContextApi, parentApi, services diff --git a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_internal_api.ts b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_internal_api.ts index e366c24a6c0e0..2c89ed463e1c5 100644 --- a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_internal_api.ts +++ b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_internal_api.ts @@ -15,6 +15,7 @@ import type { LensInternalApi, LensOverrides, LensRuntimeState, + VisualizationContext, } from '../types'; import { apiHasAbortController, apiHasLensComponentProps } from '../type_guards'; import type { UserMessage } from '../../types'; @@ -52,6 +53,18 @@ export function initializeInternalApi( // the isNewPanel won't be serialized so it will be always false after the edit panel closes applying the changes const isNewlyCreated$ = new BehaviorSubject(initialState.isNewPanel || false); + const visualizationContext$ = new BehaviorSubject({ + // doc can point to a different set of attributes for the visualization + // i.e. when inline editing or applying a suggestion + activeAttributes: initialState.attributes, + mergedSearchContext: {}, + indexPatterns: {}, + indexPatternRefs: [], + activeVisualizationState: undefined, + activeDatasourceState: undefined, + activeData: undefined, + }); + // No need to expose anything at public API right now, that would happen later on // where each initializer will pick what it needs and publish it return { @@ -65,6 +78,8 @@ export function initializeInternalApi( renderCount$, isNewlyCreated$, dataViews: dataViews$, + messages$, + validationMessages$, dispatchError: () => { hasRenderCompleted$.next(true); renderCount$.next(renderCount$.getValue() + 1); @@ -82,9 +97,7 @@ export function initializeInternalApi( updateAbortController: (abortController: AbortController | undefined) => expressionAbortController$.next(abortController), updateDataViews: (dataViews: DataView[] | undefined) => dataViews$.next(dataViews), - messages$, updateMessages: (newMessages: UserMessage[]) => messages$.next(newMessages), - validationMessages$, updateValidationMessages: (newMessages: UserMessage[]) => validationMessages$.next(newMessages), resetAllMessages: () => { messages$.next([]); @@ -116,5 +129,12 @@ export function initializeInternalApi( return displayOptions; }, + getVisualizationContext: () => visualizationContext$.getValue(), + updateVisualizationContext: (newVisualizationContext: Partial) => { + visualizationContext$.next({ + ...visualizationContext$.getValue(), + ...newVisualizationContext, + }); + }, }; } diff --git a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_visualization_context.ts b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_visualization_context.ts deleted file mode 100644 index 93d544013e710..0000000000000 --- a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_visualization_context.ts +++ /dev/null @@ -1,32 +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 type { LensInternalApi, VisualizationContext, VisualizationContextHelper } from '../types'; - -export function initializeVisualizationContext( - internalApi: LensInternalApi -): VisualizationContextHelper { - // TODO: this will likely be merged together with the state$ observable - let visualizationContext: VisualizationContext = { - doc: internalApi.attributes$.getValue(), - mergedSearchContext: {}, - indexPatterns: {}, - indexPatternRefs: [], - activeVisualizationState: undefined, - activeDatasourceState: undefined, - activeData: undefined, - }; - return { - getVisualizationContext: () => visualizationContext, - updateVisualizationContext: (newVisualizationContext: Partial) => { - visualizationContext = { - ...visualizationContext, - ...newVisualizationContext, - }; - }, - }; -} diff --git a/x-pack/plugins/lens/public/react_embeddable/lens_embeddable.tsx b/x-pack/plugins/lens/public/react_embeddable/lens_embeddable.tsx index 074ae451115b5..2fc1928dc40c8 100644 --- a/x-pack/plugins/lens/public/react_embeddable/lens_embeddable.tsx +++ b/x-pack/plugins/lens/public/react_embeddable/lens_embeddable.tsx @@ -22,7 +22,6 @@ import { initializeInspector } from './initializers/initialize_inspector'; import { initializeDashboardServices } from './initializers/initialize_dashboard_services'; import { initializeInternalApi } from './initializers/initialize_internal_api'; import { initializeSearchContext } from './initializers/initialize_search_context'; -import { initializeVisualizationContext } from './initializers/initialize_visualization_context'; import { initializeActionApi } from './initializers/initialize_actions'; import { initializeIntegrations } from './initializers/initialize_integrations'; import { initializeStateManagement } from './initializers/initialize_state_management'; @@ -61,8 +60,6 @@ export const createLensEmbeddableFactory = ( */ const internalApi = initializeInternalApi(initialState, parentApi, services); - const visualizationContextHelper = initializeVisualizationContext(internalApi); - /** * Initialize various configurations required to build all the required * parts for the Lens embeddable. @@ -108,7 +105,7 @@ export const createLensEmbeddableFactory = ( parentApi, searchContextConfig.api, dashboardConfig.api, - visualizationContextHelper, + internalApi, services ); @@ -165,8 +162,7 @@ export const createLensEmbeddableFactory = ( api, parentApi, internalApi, - services, - visualizationContextHelper + services ); const onUnmount = () => { diff --git a/x-pack/plugins/lens/public/react_embeddable/mocks/index.tsx b/x-pack/plugins/lens/public/react_embeddable/mocks/index.tsx index 6b1a21c17620a..1488f3b1b3c0b 100644 --- a/x-pack/plugins/lens/public/react_embeddable/mocks/index.tsx +++ b/x-pack/plugins/lens/public/react_embeddable/mocks/index.tsx @@ -32,19 +32,9 @@ import { LensSerializedState, VisualizationContext, } from '../types'; -import { - createMockDatasource, - createMockVisualization, - defaultDoc, - makeDefaultServices, -} from '../../mocks'; -import { - Datasource, - DatasourceMap, - UserMessage, - Visualization, - VisualizationMap, -} from '../../types'; +import { createMockDatasource, createMockVisualization, makeDefaultServices } from '../../mocks'; +import { Datasource, DatasourceMap, Visualization, VisualizationMap } from '../../types'; +import { initializeInternalApi } from '../initializers/initialize_internal_api'; const LensApiMock: LensApi = { // Static props @@ -263,7 +253,7 @@ export function createExpressionRendererMock(): jest.Mock< )); } -function getValidExpressionParams( +export function getValidExpressionParams( overrides: Partial = {} ): ExpressionWrapperProps { return { @@ -284,34 +274,11 @@ function getValidExpressionParams( }; } -const LensInternalApiMock: LensInternalApi = { - dataViews: new BehaviorSubject(undefined), - attributes$: new BehaviorSubject(defaultDoc), - overrides$: new BehaviorSubject(undefined), - disableTriggers$: new BehaviorSubject(undefined), - dataLoading$: new BehaviorSubject(undefined), - hasRenderCompleted$: new BehaviorSubject(true), - expressionParams$: new BehaviorSubject(getValidExpressionParams()), - expressionAbortController$: new BehaviorSubject(undefined), - renderCount$: new BehaviorSubject(0), - messages$: new BehaviorSubject([]), - validationMessages$: new BehaviorSubject([]), - isNewlyCreated$: new BehaviorSubject(true), - updateAttributes: jest.fn(), - updateOverrides: jest.fn(), - dispatchRenderStart: jest.fn(), - dispatchRenderComplete: jest.fn(), - updateDataLoading: jest.fn(), - updateExpressionParams: jest.fn(), - updateAbortController: jest.fn(), - updateDataViews: jest.fn(), - updateMessages: jest.fn(), - resetAllMessages: jest.fn(), - dispatchError: jest.fn(), - updateValidationMessages: jest.fn(), - setAsCreated: jest.fn(), - getDisplayOptions: jest.fn(() => ({})), -}; +const LensInternalApiMock = initializeInternalApi( + getLensRuntimeStateMock(), + {}, + makeEmbeddableServices() +); export function getLensInternalApiMock(overrides: Partial = {}): LensInternalApi { return { @@ -326,6 +293,7 @@ export function getVisualizationContextHelperMock( ) { return { getVisualizationContext: jest.fn(() => ({ + activeAttributes: getLensAttributesMock(attributesOverrides), mergedSearchContext: {}, indexPatterns: {}, indexPatternRefs: [], @@ -333,7 +301,6 @@ export function getVisualizationContextHelperMock( activeDatasourceState: undefined, activeData: undefined, ...contextOverrides, - doc: getLensAttributesMock(attributesOverrides), })), updateVisualizationContext: jest.fn(), }; diff --git a/x-pack/plugins/lens/public/react_embeddable/renderer/lens_embeddable_component.test.tsx b/x-pack/plugins/lens/public/react_embeddable/renderer/lens_embeddable_component.test.tsx index f444f429250ba..fd6d271e2c9bb 100644 --- a/x-pack/plugins/lens/public/react_embeddable/renderer/lens_embeddable_component.test.tsx +++ b/x-pack/plugins/lens/public/react_embeddable/renderer/lens_embeddable_component.test.tsx @@ -6,7 +6,7 @@ */ import { render, screen } from '@testing-library/react'; -import { getLensApiMock, getLensInternalApiMock } from '../mocks'; +import { getLensApiMock, getLensInternalApiMock, getValidExpressionParams } from '../mocks'; import { LensApi, LensInternalApi } from '../types'; import { BehaviorSubject } from 'rxjs'; import { PublishingSubject } from '@kbn/presentation-publishing'; @@ -25,6 +25,9 @@ function getDefaultProps({ internalApiOverrides = undefined, apiOverrides = undefined, }: { internalApiOverrides?: Partial; apiOverrides?: Partial } = {}) { + const internalApi = getLensInternalApiMock(internalApiOverrides); + // provide a valid expression to render + internalApi.updateExpressionParams(getValidExpressionParams()); return { internalApi: getLensInternalApiMock(internalApiOverrides), api: getLensApiMock(apiOverrides), diff --git a/x-pack/plugins/lens/public/react_embeddable/types.ts b/x-pack/plugins/lens/public/react_embeddable/types.ts index 1a4bf45b11f17..98b85860f414f 100644 --- a/x-pack/plugins/lens/public/react_embeddable/types.ts +++ b/x-pack/plugins/lens/public/react_embeddable/types.ts @@ -100,8 +100,12 @@ interface LensApiProps {} export type LensSavedObjectAttributes = Omit; +/** + * This visualization context can have a different attributes than the + * one stored in the Lens API attributes + */ export interface VisualizationContext { - doc: LensDocument | undefined; + activeAttributes: LensDocument | undefined; mergedSearchContext: ExecutionContextSearch; indexPatterns: IndexPatternMap; indexPatternRefs: IndexPatternRef[]; @@ -111,6 +115,7 @@ export interface VisualizationContext { } export interface VisualizationContextHelper { + // the doc prop here is a convenience reference to the internalApi.attributes getVisualizationContext: () => VisualizationContext; updateVisualizationContext: (newContext: Partial) => void; } @@ -399,7 +404,8 @@ export type LensApi = Simplify< // there's some overlapping between this and the LensApi but they are shared references export type LensInternalApi = Simplify< Pick & - PublishesDataViews & { + PublishesDataViews & + VisualizationContextHelper & { attributes$: PublishingSubject; overrides$: PublishingSubject; disableTriggers$: PublishingSubject; diff --git a/x-pack/plugins/lens/public/react_embeddable/user_messages/api.ts b/x-pack/plugins/lens/public/react_embeddable/user_messages/api.ts index 90061cfb7c2fe..8058f79619555 100644 --- a/x-pack/plugins/lens/public/react_embeddable/user_messages/api.ts +++ b/x-pack/plugins/lens/public/react_embeddable/user_messages/api.ts @@ -28,7 +28,6 @@ import { import { LensPublicCallbacks, LensEmbeddableStartServices, - VisualizationContext, VisualizationContextHelper, LensApi, LensInternalApi, @@ -43,7 +42,7 @@ function getUpdatedState( datasourceMap: LensEmbeddableStartServices['datasourceMap'] ) { const { - doc, + activeAttributes, mergedSearchContext, indexPatterns, indexPatternRefs, @@ -51,15 +50,15 @@ function getUpdatedState( activeDatasourceState, activeData, } = getVisualizationContext(); - const activeVisualizationId = getActiveVisualizationIdFromDoc(doc); - const activeDatasourceId = getActiveDatasourceIdFromDoc(doc); + const activeVisualizationId = getActiveVisualizationIdFromDoc(activeAttributes); + const activeDatasourceId = getActiveDatasourceIdFromDoc(activeAttributes); const activeDatasource = activeDatasourceId ? datasourceMap[activeDatasourceId] : null; const activeVisualization = activeVisualizationId ? visualizationMap[activeVisualizationId] : undefined; const dataViewObject = getInitialDataViewsObject(indexPatterns, indexPatternRefs); return { - doc, + activeAttributes, mergedSearchContext, activeDatasource, activeVisualization, @@ -100,7 +99,6 @@ function getWarningMessages( export function buildUserMessagesHelpers( api: LensApi, internalApi: LensInternalApi, - getVisualizationContext: () => VisualizationContext, { coreStart, data, visualizationMap, datasourceMap }: LensEmbeddableStartServices, onBeforeBadgesRender: LensPublicCallbacks['onBeforeBadgesRender'], spaces?: SpacesApi, @@ -131,7 +129,7 @@ export function buildUserMessagesHelpers( const getUserMessages: UserMessagesGetter = (locationId, filters) => { const { - doc, + activeAttributes, activeVisualizationState, activeVisualization, activeVisualizationId, @@ -141,12 +139,12 @@ export function buildUserMessagesHelpers( dataViewObject, mergedSearchContext, activeData, - } = getUpdatedState(getVisualizationContext, visualizationMap, datasourceMap); + } = getUpdatedState(internalApi.getVisualizationContext, visualizationMap, datasourceMap); const userMessages: UserMessage[] = []; userMessages.push( ...getApplicationUserMessages({ - visualizationType: doc?.visualizationType, + visualizationType: activeAttributes?.visualizationType, visualizationState: { state: activeVisualizationState, activeId: activeVisualizationId, @@ -162,7 +160,7 @@ export function buildUserMessagesHelpers( }) ); - if (!doc || !activeDatasourceState || !activeVisualizationState) { + if (!activeAttributes || !activeDatasourceState || !activeVisualizationState) { return userMessages; } @@ -178,7 +176,7 @@ export function buildUserMessagesHelpers( datasourceMap, dataViewObject.indexPatterns ), - query: doc.state.query, + query: activeAttributes.state.query, filters: mergedSearchContext.filters ?? [], dateRange: { fromDate: mergedSearchContext.timeRange?.from ?? '', @@ -278,7 +276,7 @@ export function buildUserMessagesHelpers( updateWarnings: () => { addUserMessages( getWarningMessages( - getUpdatedState(getVisualizationContext, visualizationMap, datasourceMap), + getUpdatedState(internalApi.getVisualizationContext, visualizationMap, datasourceMap), api.adapters$.getValue(), data )