From db72043544ed18d8bca28d37376696e4890bda0b Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Mon, 21 Jul 2025 09:30:28 -0700 Subject: [PATCH 1/2] [Lens] Fix Firefox inline editor scroll (#228625) ## Summary Fixes an issues in Firefox where the Lens flyout configuration was not scrollable. https://github.com/user-attachments/assets/e8e57fc6-b2c8-4354-be17-d41cb42317df Fixes #227939 ### Checklist - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. Co-authored-by: Marta Bondyra <4283304+mbondyra@users.noreply.github.com> (cherry picked from commit 90d35e0b1d9757abf5063c3ac866d2f707b15ee6) # Conflicts: # x-pack/platform/plugins/shared/lens/public/react_embeddable/mount.tsx --- .../get_edit_lens_configuration.tsx | 253 ++++++++++++++++++ .../open_lens_config/helpers.scss | 8 +- 2 files changed, 256 insertions(+), 5 deletions(-) create mode 100644 x-pack/platform/plugins/shared/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx diff --git a/x-pack/platform/plugins/shared/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx b/x-pack/platform/plugins/shared/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx new file mode 100644 index 0000000000000..3564016e1e3dd --- /dev/null +++ b/x-pack/platform/plugins/shared/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx @@ -0,0 +1,253 @@ +/* + * 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 React, { useCallback, useState } from 'react'; +import { EuiFlyout, EuiLoadingSpinner, EuiOverlayMask } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { Provider } from 'react-redux'; +import type { MiddlewareAPI, Dispatch, Action } from '@reduxjs/toolkit'; +import { css } from '@emotion/react'; +import type { CoreStart } from '@kbn/core/public'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; +import { isEqual } from 'lodash'; +import { RootDragDropProvider } from '@kbn/dom-drag-drop'; +import { TypedLensSerializedState } from '../../../react_embeddable/types'; +import type { LensPluginStartDependencies } from '../../../plugin'; +import { + makeConfigureStore, + LensRootStore, + loadInitial, + initExisting, + initEmpty, + type LensStoreDeps, +} from '../../../state_management'; +import { generateId } from '../../../id_generator'; +import type { DatasourceMap, VisualizationMap } from '../../../types'; +import { LensEditConfigurationFlyout } from './lens_configuration_flyout'; +import type { EditConfigPanelProps } from './types'; +import { LensDocumentService, type LensDocument } from '../../../persistence'; +import { DOC_TYPE } from '../../../../common/constants'; + +export type EditLensConfigurationProps = Omit< + EditConfigPanelProps, + | 'startDependencies' + | 'coreStart' + | 'visualizationMap' + | 'datasourceMap' + | 'saveByRef' + | 'setCurrentAttributes' + | 'previousAttributes' +>; +function LoadingSpinnerWithOverlay() { + return ( + + + + ); +} + +type UpdaterType = ( + datasourceState: unknown, + visualizationState: unknown, + visualizationType?: string +) => void; + +// exported for testing +export const updatingMiddleware = + (updater: UpdaterType) => (store: MiddlewareAPI) => (next: Dispatch) => (action: Action) => { + const { + datasourceStates: prevDatasourceStates, + visualization: prevVisualization, + activeDatasourceId: prevActiveDatasourceId, + } = store.getState().lens; + next(action); + const { datasourceStates, visualization, activeDatasourceId } = store.getState().lens; + if ( + prevActiveDatasourceId !== activeDatasourceId || + !isEqual( + prevDatasourceStates[prevActiveDatasourceId].state, + datasourceStates[activeDatasourceId].state + ) || + !isEqual(prevVisualization, visualization) + ) { + // ignore the actions that initialize the store with the state from the attributes + if (initExisting.match(action) || initEmpty.match(action)) { + return; + } + + updater( + datasourceStates[activeDatasourceId].state, + visualization.state, + visualization.activeId + ); + } + }; + +const MaybeWrapper = ({ + wrapInFlyout, + closeFlyout, + children, +}: { + wrapInFlyout?: boolean; + children: JSX.Element; + closeFlyout?: () => void; +}) => { + if (!wrapInFlyout) { + return children; + } + return ( + { + closeFlyout?.(); + }} + aria-label={i18n.translate('xpack.lens.config.editLabel', { + defaultMessage: 'Edit configuration', + })} + role="dialog" + size="s" + hideCloseButton + css={css` + clip-path: none; // need to override the eui-flyout clip-path for dnd outside of the flyout + `} + > + {children} + + ); +}; + +export async function getEditLensConfiguration( + coreStart: CoreStart, + startDependencies: LensPluginStartDependencies, + visualizationMap?: VisualizationMap, + datasourceMap?: DatasourceMap +) { + const { getLensServices, getLensAttributeService } = await import('../../../async_services'); + const lensServices = await getLensServices( + coreStart, + startDependencies, + getLensAttributeService(startDependencies) + ); + + return ({ + attributes, + updatePanelState, + updateSuggestion, + closeFlyout, + wrapInFlyout, + datasourceId, + panelId, + savedObjectId, + dataLoading$, + lensAdapters, + updateByRefInput, + navigateToLensEditor, + displayFlyoutHeader, + canEditTextBasedQuery, + isNewPanel, + hidesSuggestions, + onApply, + onCancel, + hideTimeFilterInfo, + isReadOnly, + parentApi, + }: EditLensConfigurationProps) => { + if (!lensServices || !datasourceMap || !visualizationMap) { + return ; + } + const [currentAttributes, setCurrentAttributes] = + useState(attributes); + + /** + * During inline editing of a by reference panel, the panel is converted to a by value one. + * When the user applies the changes we save them to the Lens SO + */ + const saveByRef = useCallback( + async (attrs: LensDocument) => { + const lensDocumentService = new LensDocumentService(lensServices.contentManagement); + await lensDocumentService.save({ + ...attrs, + savedObjectId, + type: DOC_TYPE, + }); + }, + [savedObjectId] + ); + const datasourceState = currentAttributes.state.datasourceStates[datasourceId]; + const storeDeps: LensStoreDeps = { + lensServices, + datasourceMap, + visualizationMap, + initialContext: + datasourceState && 'initialContext' in datasourceState + ? datasourceState.initialContext + : undefined, + visualizationType: attributes.visualizationType, + }; + const lensStore: LensRootStore = makeConfigureStore( + storeDeps, + undefined, + updatingMiddleware(updatePanelState) + ); + lensStore.dispatch( + loadInitial({ + initialInput: { + attributes: currentAttributes, + id: panelId ?? generateId(), + }, + inlineEditing: true, + }) + ); + + const configPanelProps: EditConfigPanelProps = { + attributes: currentAttributes, + updatePanelState, + updateSuggestion, + closeFlyout, + datasourceId, + coreStart, + startDependencies, + visualizationMap, + dataLoading$, + lensAdapters, + datasourceMap, + saveByRef, + savedObjectId, + updateByRefInput, + navigateToLensEditor, + displayFlyoutHeader, + canEditTextBasedQuery, + hidesSuggestions, + setCurrentAttributes, + isNewPanel, + onApply, + onCancel, + hideTimeFilterInfo, + isReadOnly, + parentApi, + panelId, + }; + + return ( + + + + + + + + + + + + ); + }; +} diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/helpers.scss b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/helpers.scss index ae9a21f3f63f7..59ed3e6837f72 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/helpers.scss +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/helpers.scss @@ -1,12 +1,10 @@ // styles needed to display extra drop targets that are outside of the config panel main area while also allowing to scroll vertically .lnsConfigPanel__overlay { - clip-path: polygon(-100% 0, 100% 0, 100% 100%, -100% 100%); + clip-path:none; // need to override the eui-flyout clip-path max-inline-size: $euiSizeXXL * 20; min-inline-size: $euiSizeXXL * 8; background: $euiColorLightestShade; - @include euiBreakpoint('xs', 's', 'm') { - clip-path: none; - } + .kbnOverlayMountWrapper { padding-left: $euiFormMaxWidth; margin-left: -$euiFormMaxWidth; @@ -21,4 +19,4 @@ .euiFlyoutBody__overflow { transform: initial; } -} \ No newline at end of file +} From c0cade768f42a1eb82076313594582e01f076673 Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Wed, 23 Jul 2025 08:39:28 -0500 Subject: [PATCH 2/2] chore: whoops fixed wrong file --- .../get_edit_lens_configuration.tsx | 253 ------------------ .../get_edit_lens_configuration.tsx | 2 +- 2 files changed, 1 insertion(+), 254 deletions(-) delete mode 100644 x-pack/platform/plugins/shared/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx diff --git a/x-pack/platform/plugins/shared/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx b/x-pack/platform/plugins/shared/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx deleted file mode 100644 index 3564016e1e3dd..0000000000000 --- a/x-pack/platform/plugins/shared/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx +++ /dev/null @@ -1,253 +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 React, { useCallback, useState } from 'react'; -import { EuiFlyout, EuiLoadingSpinner, EuiOverlayMask } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { Provider } from 'react-redux'; -import type { MiddlewareAPI, Dispatch, Action } from '@reduxjs/toolkit'; -import { css } from '@emotion/react'; -import type { CoreStart } from '@kbn/core/public'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; -import { isEqual } from 'lodash'; -import { RootDragDropProvider } from '@kbn/dom-drag-drop'; -import { TypedLensSerializedState } from '../../../react_embeddable/types'; -import type { LensPluginStartDependencies } from '../../../plugin'; -import { - makeConfigureStore, - LensRootStore, - loadInitial, - initExisting, - initEmpty, - type LensStoreDeps, -} from '../../../state_management'; -import { generateId } from '../../../id_generator'; -import type { DatasourceMap, VisualizationMap } from '../../../types'; -import { LensEditConfigurationFlyout } from './lens_configuration_flyout'; -import type { EditConfigPanelProps } from './types'; -import { LensDocumentService, type LensDocument } from '../../../persistence'; -import { DOC_TYPE } from '../../../../common/constants'; - -export type EditLensConfigurationProps = Omit< - EditConfigPanelProps, - | 'startDependencies' - | 'coreStart' - | 'visualizationMap' - | 'datasourceMap' - | 'saveByRef' - | 'setCurrentAttributes' - | 'previousAttributes' ->; -function LoadingSpinnerWithOverlay() { - return ( - - - - ); -} - -type UpdaterType = ( - datasourceState: unknown, - visualizationState: unknown, - visualizationType?: string -) => void; - -// exported for testing -export const updatingMiddleware = - (updater: UpdaterType) => (store: MiddlewareAPI) => (next: Dispatch) => (action: Action) => { - const { - datasourceStates: prevDatasourceStates, - visualization: prevVisualization, - activeDatasourceId: prevActiveDatasourceId, - } = store.getState().lens; - next(action); - const { datasourceStates, visualization, activeDatasourceId } = store.getState().lens; - if ( - prevActiveDatasourceId !== activeDatasourceId || - !isEqual( - prevDatasourceStates[prevActiveDatasourceId].state, - datasourceStates[activeDatasourceId].state - ) || - !isEqual(prevVisualization, visualization) - ) { - // ignore the actions that initialize the store with the state from the attributes - if (initExisting.match(action) || initEmpty.match(action)) { - return; - } - - updater( - datasourceStates[activeDatasourceId].state, - visualization.state, - visualization.activeId - ); - } - }; - -const MaybeWrapper = ({ - wrapInFlyout, - closeFlyout, - children, -}: { - wrapInFlyout?: boolean; - children: JSX.Element; - closeFlyout?: () => void; -}) => { - if (!wrapInFlyout) { - return children; - } - return ( - { - closeFlyout?.(); - }} - aria-label={i18n.translate('xpack.lens.config.editLabel', { - defaultMessage: 'Edit configuration', - })} - role="dialog" - size="s" - hideCloseButton - css={css` - clip-path: none; // need to override the eui-flyout clip-path for dnd outside of the flyout - `} - > - {children} - - ); -}; - -export async function getEditLensConfiguration( - coreStart: CoreStart, - startDependencies: LensPluginStartDependencies, - visualizationMap?: VisualizationMap, - datasourceMap?: DatasourceMap -) { - const { getLensServices, getLensAttributeService } = await import('../../../async_services'); - const lensServices = await getLensServices( - coreStart, - startDependencies, - getLensAttributeService(startDependencies) - ); - - return ({ - attributes, - updatePanelState, - updateSuggestion, - closeFlyout, - wrapInFlyout, - datasourceId, - panelId, - savedObjectId, - dataLoading$, - lensAdapters, - updateByRefInput, - navigateToLensEditor, - displayFlyoutHeader, - canEditTextBasedQuery, - isNewPanel, - hidesSuggestions, - onApply, - onCancel, - hideTimeFilterInfo, - isReadOnly, - parentApi, - }: EditLensConfigurationProps) => { - if (!lensServices || !datasourceMap || !visualizationMap) { - return ; - } - const [currentAttributes, setCurrentAttributes] = - useState(attributes); - - /** - * During inline editing of a by reference panel, the panel is converted to a by value one. - * When the user applies the changes we save them to the Lens SO - */ - const saveByRef = useCallback( - async (attrs: LensDocument) => { - const lensDocumentService = new LensDocumentService(lensServices.contentManagement); - await lensDocumentService.save({ - ...attrs, - savedObjectId, - type: DOC_TYPE, - }); - }, - [savedObjectId] - ); - const datasourceState = currentAttributes.state.datasourceStates[datasourceId]; - const storeDeps: LensStoreDeps = { - lensServices, - datasourceMap, - visualizationMap, - initialContext: - datasourceState && 'initialContext' in datasourceState - ? datasourceState.initialContext - : undefined, - visualizationType: attributes.visualizationType, - }; - const lensStore: LensRootStore = makeConfigureStore( - storeDeps, - undefined, - updatingMiddleware(updatePanelState) - ); - lensStore.dispatch( - loadInitial({ - initialInput: { - attributes: currentAttributes, - id: panelId ?? generateId(), - }, - inlineEditing: true, - }) - ); - - const configPanelProps: EditConfigPanelProps = { - attributes: currentAttributes, - updatePanelState, - updateSuggestion, - closeFlyout, - datasourceId, - coreStart, - startDependencies, - visualizationMap, - dataLoading$, - lensAdapters, - datasourceMap, - saveByRef, - savedObjectId, - updateByRefInput, - navigateToLensEditor, - displayFlyoutHeader, - canEditTextBasedQuery, - hidesSuggestions, - setCurrentAttributes, - isNewPanel, - onApply, - onCancel, - hideTimeFilterInfo, - isReadOnly, - parentApi, - panelId, - }; - - return ( - - - - - - - - - - - - ); - }; -} diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx index 205aa74aaee24..9c6b761123b54 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx @@ -184,7 +184,7 @@ export async function getEditLensConfiguration( size="s" hideCloseButton css={css` - clip-path: polygon(-100% 0, 100% 0, 100% 100%, -100% 100%); + clip-path: none; // need to override the eui-flyout clip-path for dnd outside of the flyout `} > {children}