From c521c1c139a4a661c76be83497bc18affdda0857 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Thu, 19 Dec 2024 20:34:49 +0100 Subject: [PATCH] [Lens][Embeddable] Apply the correct references for filters (#204047) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes #180726 Fix the filter references when inline editing with the correct ones. I've tried to reduce the fix to a minimal `extract` wrapper, but unfortunately that is not possible due to some shared logic who rely on the passed filters references and need to be injected. So, why not injecting them and instead rely on the search context api? Right now there's no difference, but formally the `api.filters$` is the right place to get the latest version, and if in the future the `Edit filters` flows would change, this api should be the go-to place to have the right value. Why not adding a FTR? There's a bigger problem with the panel filters action who has a dynamic `data-test-subj` value which is impossible to get right now. I would rather prefer to fix that first and then add some tests in general for multiple scenarios in Lens. ## Testing it locally * Create a viz with a filter in the editor, save and return to dashboard * Check the filters are shown correctly in the dashboard panel * Edit inline and change the chart type. Apply changes * Check the filters are shown correctly * Now "edit" in the editor without changing anything * Check the filter can be edited correctly (no empty popover) ✅ or 💥 * Save and return to dashboard * Check the filters are shown correctly ✅ or 💥 --- .../initializers/initialize_edit.tsx | 32 ++++++++++++++++++- .../initializers/initialize_search_context.ts | 27 +++++++++++----- .../react_embeddable/lens_embeddable.tsx | 9 +++++- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_edit.tsx b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_edit.tsx index 81372dad339f7..4674fb84c2635 100644 --- a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_edit.tsx +++ b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_edit.tsx @@ -37,6 +37,7 @@ import { setupPanelManagement } from '../inline_editing/panel_management'; import { mountInlineEditPanel } from '../inline_editing/mount'; import { StateManagementConfig } from './initialize_state_management'; import { apiPublishesInlineEditingCapabilities } from '../type_guards'; +import { SearchContextConfig } from './initialize_search_context'; function getSupportedTriggers( getState: GetStateType, @@ -61,6 +62,7 @@ export function initializeEditApi( internalApi: LensInternalApi, stateApi: StateManagementConfig['api'], inspectorApi: LensInspectorAdapters, + searchContextApi: SearchContextConfig['api'], isTextBasedLanguage: (currentState: LensRuntimeState) => boolean, startDependencies: LensEmbeddableStartServices, parentApi?: unknown @@ -126,9 +128,34 @@ export function initializeEditApi( stateApi.updateSavedObjectId(newState.savedObjectId); }; + // Wrap the getState() when inline editing and make sure that the filters in the attributes + // are properly injected with the correct references to avoid issues when saving/navigating to the full editor + const getStateWithInjectedFilters = () => { + const currentState = getState(); + // use the search context api here for filters for 2 reasons: + // * the filters here have the correct references already injected + // * the edit filters flow may change in the future and this is the right place to get the filters + const currentFilters = searchContextApi.filters$.getValue() ?? []; + // if there are no filters, avoid to copy the attributes + if (!currentFilters.length) { + return currentState; + } + // otherwise make sure to inject the references into filters + return { + ...currentState, + attributes: { + ...currentState.attributes, + state: { + ...currentState.attributes.state, + filters: currentFilters, + }, + }, + }; + }; + const openInlineEditor = prepareInlineEditPanel( initialState, - getState, + getStateWithInjectedFilters, updateState, internalApi, panelManagementApi, @@ -205,6 +232,9 @@ export function initializeEditApi( const rootEmbeddable = parentApi; const overlayTracker = tracksOverlays(rootEmbeddable) ? rootEmbeddable : undefined; const ConfigPanel = await openInlineEditor({ + // the getState() here contains the wrong filters references + // but the input attributes are correct as openInlineEditor() handler is using + // the getStateWithInjectedFilters() function onApply: (attributes: LensRuntimeState['attributes']) => updateState({ ...getState(), attributes }), // restore the first state found when the panel opened diff --git a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_search_context.ts b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_search_context.ts index 1a608de11e230..1d95fd49b3f55 100644 --- a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_search_context.ts +++ b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_search_context.ts @@ -18,18 +18,26 @@ import { apiPublishesSearchSession, } from '@kbn/presentation-publishing/interfaces/fetch/publishes_search_session'; import { buildObservableVariable } from '../helper'; -import { LensInternalApi, LensRuntimeState, LensUnifiedSearchContext } from '../types'; +import { + LensEmbeddableStartServices, + LensInternalApi, + LensRuntimeState, + LensUnifiedSearchContext, +} from '../types'; -export function initializeSearchContext( - initialState: LensRuntimeState, - internalApi: LensInternalApi, - parentApi: unknown -): { +export interface SearchContextConfig { api: PublishesUnifiedSearch & PublishesSearchSession; comparators: StateComparators; serialize: () => LensUnifiedSearchContext; cleanup: () => void; -} { +} + +export function initializeSearchContext( + initialState: LensRuntimeState, + internalApi: LensInternalApi, + parentApi: unknown, + { injectFilterReferences }: LensEmbeddableStartServices +): SearchContextConfig { const [searchSessionId$] = buildObservableVariable( apiPublishesSearchSession(parentApi) ? parentApi.searchSessionId$ : undefined ); @@ -38,7 +46,10 @@ export function initializeSearchContext( const [lastReloadRequestTime] = buildObservableVariable(undefined); - const [filters$] = buildObservableVariable(attributes.state.filters); + // Make sure the panel access the filters with the correct references + const [filters$] = buildObservableVariable( + injectFilterReferences(attributes.state.filters, attributes.references) + ); const [query$] = buildObservableVariable( attributes.state.query 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 2fc1928dc40c8..c193e02c06f0e 100644 --- a/x-pack/plugins/lens/public/react_embeddable/lens_embeddable.tsx +++ b/x-pack/plugins/lens/public/react_embeddable/lens_embeddable.tsx @@ -84,6 +84,13 @@ export const createLensEmbeddableFactory = ( const inspectorConfig = initializeInspector(services); + const searchContextConfig = initializeSearchContext( + initialState, + internalApi, + parentApi, + services + ); + const editConfig = initializeEditApi( uuid, initialState, @@ -91,12 +98,12 @@ export const createLensEmbeddableFactory = ( internalApi, stateConfig.api, inspectorConfig.api, + searchContextConfig.api, isTextBasedLanguage, services, parentApi ); - const searchContextConfig = initializeSearchContext(initialState, internalApi, parentApi); const integrationsConfig = initializeIntegrations(getState, services); const actionsConfig = initializeActionApi( uuid,