diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx index b62ea4271ce1c..afee50de06675 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { ReactExpressionRendererProps } from '../../../../../../../src/plugins/expressions/public'; -import { DatasourcePublicAPI, FramePublicAPI, Visualization } from '../../../types'; +import { FramePublicAPI, Visualization } from '../../../types'; import { createMockVisualization, createMockDatasource, @@ -177,6 +177,8 @@ describe('workspace_panel', () => { instance = mounted.instance; + instance.update(); + expect(instance.find(expressionRendererMock).prop('expression')).toMatchInlineSnapshot(` "kibana | lens_merge_tables layerIds=\\"first\\" tables={datasource} @@ -184,26 +186,14 @@ describe('workspace_panel', () => { `); }); - it('should use applied state when available (when auto-apply is disabled)', async () => { + it('should give user control when auto-apply disabled', async () => { const framePublicAPI = createMockFramePublicAPI(); framePublicAPI.datasourceLayers = { first: mockDatasource.publicAPIMock, }; - framePublicAPI.appliedDatasourceLayers = { - first: { ...mockDatasource.publicAPIMock, applied: true } as DatasourcePublicAPI, - }; - mockDatasource.toExpression.mockReturnValue('datasource'); mockDatasource.getLayers.mockReturnValue(['first']); - const toExpression = jest.fn( - ( - state: unknown, - datasourceLayers: Record, - attributes?: Partial<{ title: string; description: string }> - ) => 'testVis' - ); - const mounted = await mountWithProvider( { }} framePublicAPI={framePublicAPI} visualizationMap={{ - testVis: { ...mockVisualization, toExpression }, + testVis: { ...mockVisualization, toExpression: () => 'testVis' }, }} ExpressionRenderer={expressionRendererMock} />, { preloadedState: { - appliedState: { - activeDatasourceId: 'testDatasource', - visualization: { - activeId: 'testVis', - state: { applied: true }, - }, - datasourceStates: { - [mockDatasource.id]: { isLoading: false, state: { applied: true } }, - }, - }, + autoApplyDisabled: true, }, } ); instance = mounted.instance; - expect(toExpression).toHaveBeenCalled(); - const [appliedVisualizationState, appliedDatasourceLayers] = toExpression.mock.calls[0]; - expect((appliedVisualizationState as { applied: boolean }).applied).toBe(true); - expect( - (appliedDatasourceLayers?.first as DatasourcePublicAPI & { applied: boolean }).applied - ).toBe(true); + instance.update(); + + // allows initial render + expect(instance.find(expressionRendererMock).prop('expression')).toMatchInlineSnapshot(` + "kibana + | lens_merge_tables layerIds=\\"first\\" tables={datasource} + | testVis" + `); - // START NEW SCENARIO — make sure it switches back to using working state when auto-apply is reenabled + mockDatasource.toExpression.mockReturnValue('new-datasource'); + instance.setProps({ + visualizationMap: { + testVis: { ...mockVisualization, toExpression: () => 'new-vis' }, + }, + }); + + // nothing should change + expect(instance.find(expressionRendererMock).prop('expression')).toMatchInlineSnapshot(` + "kibana + | lens_merge_tables layerIds=\\"first\\" tables={datasource} + | testVis" + `); + + mounted.lensStore.dispatch(applyChanges()); + instance.update(); + + // should update + expect(instance.find(expressionRendererMock).prop('expression')).toMatchInlineSnapshot(` + "kibana + | lens_merge_tables layerIds=\\"first\\" tables={new-datasource} + | new-vis" + `); - // have to do this part manually in the test, but in the application it will happen in the frame API selector which will be called in the parent component tree + mockDatasource.toExpression.mockReturnValue('other-new-datasource'); instance.setProps({ - framePublicAPI: { ...framePublicAPI, appliedDatasourceLayers: undefined }, + visualizationMap: { + testVis: { ...mockVisualization, toExpression: () => 'other-new-vis' }, + }, }); - toExpression.mockClear(); + + // should not update + expect(instance.find(expressionRendererMock).prop('expression')).toMatchInlineSnapshot(` + "kibana + | lens_merge_tables layerIds=\\"first\\" tables={new-datasource} + | new-vis" + `); mounted.lensStore.dispatch(enableAutoApply()); + instance.update(); - expect(toExpression).toHaveBeenCalledTimes(1); - const [visualizationState, datasourceLayers] = toExpression.mock.calls[0]; - expect((visualizationState as { applied: boolean }).applied).toBeUndefined(); - expect( - (datasourceLayers?.first as DatasourcePublicAPI & { applied: boolean }).applied - ).toBeUndefined(); + // reenabling auto-apply triggers an update as well + expect(instance.find(expressionRendererMock).prop('expression')).toMatchInlineSnapshot(` + "kibana + | lens_merge_tables layerIds=\\"first\\" tables={other-new-datasource} + | other-new-vis" + `); + }); + + it('should allow empty workspace as initial render when auto-apply disabled', async () => { + mockVisualization.toExpression.mockReturnValue('testVis'); + const framePublicAPI = createMockFramePublicAPI(); + framePublicAPI.datasourceLayers = { + first: mockDatasource.publicAPIMock, + }; + + const mounted = await mountWithProvider( + , + { + preloadedState: { + autoApplyDisabled: true, + }, + } + ); + + instance = mounted.instance; + + expect(instance.exists('[data-test-subj="empty-workspace"]')).toBeTruthy(); }); it('should execute a trigger on expression event', async () => { @@ -371,6 +415,7 @@ describe('workspace_panel', () => { } ); instance = mounted.instance; + instance.update(); const ast = fromExpression(instance.find(expressionRendererMock).prop('expression') as string); @@ -654,6 +699,9 @@ describe('workspace_panel', () => { /> ); instance = mounted.instance; + act(() => { + instance.update(); + }); expect(instance.find('[data-test-subj="configuration-failure"]').exists()).toBeTruthy(); expect(instance.find(expressionRendererMock)).toHaveLength(0); @@ -779,7 +827,7 @@ describe('workspace_panel', () => { expect(showingErrors()).toBeFalsy(); - // introduce some issues in working state + // introduce some issues lensStore.dispatch( updateDatasourceState({ datasourceId: 'testDatasource', diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index c98b40c275b03..81a3990fdd5fd 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -38,7 +38,6 @@ import { DatasourceMap, DatasourceFixAction, Suggestion, - Visualization, } from '../../../types'; import { DragDrop, DragContext, DragDropIdentifier } from '../../../drag_drop'; import { switchToSuggestion } from '../suggestion_helpers'; @@ -67,14 +66,12 @@ import { selectDatasourceStates, selectActiveDatasourceId, selectSearchSessionId, - selectAppliedState, - VisualizationState, - DatasourceStates, - AppliedState, - selectChangesApplied, + selectAutoApplyEnabled, + selectApplyChangesCounter, } from '../../../state_management'; import type { LensInspector } from '../../../lens_inspector_service'; import { inferTimeField } from '../../../utils'; +import { setChangesApplied } from '../../../state_management/lens_slice'; export interface WorkspacePanelProps { visualizationMap: VisualizationMap; @@ -94,6 +91,8 @@ interface WorkspaceState { fixAction?: DatasourceFixAction; }>; expandError: boolean; + initialRenderComplete: boolean; + lastApplyChangesCounter: number; } const dropProps = { @@ -124,74 +123,57 @@ export const WorkspacePanel = React.memo(function WorkspacePanel(props: Workspac ); }); -/** - * This function returns the appropriate arguments depending on whether - * or not appliedState is passed in - */ -const getBuildExpressionArgs = ({ - appliedState, - visualization, - datasourceStates, - activeDatasourceId, - framePublicAPI: { datasourceLayers, appliedDatasourceLayers }, +let expressionToRender: null | undefined | string; + +// Exported for testing purposes only. +export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ + framePublicAPI, visualizationMap, -}: { - appliedState?: AppliedState; - visualization: VisualizationState; - datasourceStates: DatasourceStates; - activeDatasourceId: string | null; - framePublicAPI: FramePublicAPI; - visualizationMap: VisualizationMap; -}) => { - const visualizationState = appliedState?.visualization || visualization; - return { - visualization: visualizationState, - datasourceStates: appliedState?.datasourceStates || datasourceStates, - activeDatasourceId: appliedState?.activeDatasourceId || activeDatasourceId, - datasourceLayers: appliedDatasourceLayers || datasourceLayers, - activeVisualization: visualization.activeId - ? visualizationMap[visualizationState.activeId as string] - : null, - }; -}; + datasourceMap, + core, + plugins, + ExpressionRenderer: ExpressionRendererComponent, + suggestionForDraggedField, + lensInspector, +}: Omit & { + suggestionForDraggedField: Suggestion | undefined; +}) { + const dispatchLens = useLensDispatch(); + const isFullscreen = useLensSelector(selectIsFullscreenDatasource); + const visualization = useLensSelector(selectVisualization); + const activeDatasourceId = useLensSelector(selectActiveDatasourceId); + const datasourceStates = useLensSelector(selectDatasourceStates); + const autoApplyEnabled = useLensSelector(selectAutoApplyEnabled); + const applyChangesCounter = useLensSelector(selectApplyChangesCounter); -interface ErrorDescription { - shortMessage: string; - longMessage: React.ReactNode; -} + const [localState, setLocalState] = useState({ + expressionBuildError: undefined, + expandError: false, + lastApplyChangesCounter: 0, + initialRenderComplete: false, + }); -const checkConfiguration = ({ - activeVisualization, - visualization, - activeDatasourceId, - datasourceStates, - datasourceMap, - datasourceLayers, -}: { - activeVisualization: Visualization | null; - visualization: VisualizationState; - activeDatasourceId: string | null; - datasourceMap: DatasourceMap; - datasourceStates: DatasourceStates; - datasourceLayers: FramePublicAPI['datasourceLayers']; -}) => - validateDatasourceAndVisualization( - activeDatasourceId ? datasourceMap[activeDatasourceId] : null, - activeDatasourceId && datasourceStates[activeDatasourceId]?.state, - activeVisualization, - visualization.state, - { datasourceLayers } - ); + useEffect(() => { + return () => { + expressionToRender = null; + }; + }, []); + + const triggerApply = applyChangesCounter !== localState.lastApplyChangesCounter; + + if (triggerApply) { + setLocalState((s) => ({ ...s, lastApplyChangesCounter: applyChangesCounter })); + } + + const shouldApplyExpression = + autoApplyEnabled || !localState.initialRenderComplete || triggerApply; + + const { datasourceLayers } = framePublicAPI; + + const activeVisualization = visualization.activeId + ? visualizationMap[visualization.activeId] + : null; -const checkMissingRefs = ({ - activeDatasourceId, - datasourceMap, - datasourceStates, -}: { - activeDatasourceId: string | null; - datasourceMap: DatasourceMap; - datasourceStates: DatasourceStates; -}) => { const missingIndexPatterns = getMissingIndexPattern( activeDatasourceId ? datasourceMap[activeDatasourceId] : null, activeDatasourceId ? datasourceStates[activeDatasourceId] : null @@ -213,264 +195,93 @@ const checkMissingRefs = ({ ] : []; - return missingRefsErrors; -}; - -const checkUnknownVis = ( - visualization: VisualizationState, - activeVisualization: Visualization | null -) => { - return visualization.activeId && !activeVisualization; -}; - -const generateExpression = ({ - errors: { hasConfigurationValidationError, hasMissingRefsErrors, hasUnknownVisError }, - activeVisualization, - visualization, - datasourceMap, - datasourceStates, - datasourceLayers, -}: { - errors: { - hasConfigurationValidationError: boolean; - hasMissingRefsErrors: boolean; - hasUnknownVisError: boolean; - }; - activeVisualization: Visualization | null; - visualization: VisualizationState; - datasourceMap: DatasourceMap; - datasourceStates: DatasourceStates; - datasourceLayers: FramePublicAPI['datasourceLayers']; -}) => { - let expression; - let expressionBuildError: ErrorDescription[] | undefined; - if (!hasConfigurationValidationError && !hasMissingRefsErrors && !hasUnknownVisError) { - try { - const ast = buildExpression({ - visualization: activeVisualization, - visualizationState: visualization.state, - datasourceMap, - datasourceStates, - datasourceLayers, - }); - - if (ast) { - // expression has to be turned into a string for dirty checking - if the ast is rebuilt, - // turning it into a string will make sure the expression renderer only re-renders if the - // expression actually changed. - expression = toExpression(ast); - } else { - expression = null; - } - } catch (e) { - const buildMessages = activeVisualization?.getErrorMessages(visualization.state); - const defaultMessage = { - shortMessage: i18n.translate('xpack.lens.editorFrame.buildExpressionError', { - defaultMessage: 'An unexpected error occurred while preparing the chart', - }), - longMessage: e.toString(), - }; - // Most likely an error in the expression provided by a datasource or visualization - expressionBuildError = buildMessages ?? [defaultMessage]; - } - } - if (hasUnknownVisError) { - expressionBuildError = [getUnknownVisualizationTypeError(visualization.activeId!)]; - } - - return { expression, expressionBuildError }; -}; - -/** - * Checks whether the user's unapplied changes are saveable (can generate a valid expression) - * - * Since state is applied when the user saves the visualization, the save button - * should be disabled when the visualization configuration that would actually be saved is invalid - */ -const areUnappliedChangesSaveable = ({ - _visualization, - _datasourceStates, - _activeDatasourceId, - visualizationMap, - datasourceMap, - framePublicAPI, -}: { - _visualization: VisualizationState; - _datasourceStates: DatasourceStates; - _activeDatasourceId: string | null; - visualizationMap: VisualizationMap; - datasourceMap: DatasourceMap; - framePublicAPI: FramePublicAPI; -}) => { - const unappliedArgs = getBuildExpressionArgs({ - appliedState: undefined, // get the UNAPPLIED state args - visualization: _visualization, - datasourceStates: _datasourceStates, - activeDatasourceId: _activeDatasourceId, - framePublicAPI, - visualizationMap, - }); - - const { activeDatasourceId, datasourceStates, visualization, activeVisualization } = - unappliedArgs; - - const configurationValidationError = checkConfiguration({ - activeVisualization, - visualization, - activeDatasourceId, - datasourceStates, - datasourceMap, - datasourceLayers: framePublicAPI.datasourceLayers, - }); - - const missingRefsErrors = checkMissingRefs({ - activeDatasourceId, - datasourceStates, - datasourceMap, - }); - - const unknownVisError = checkUnknownVis(visualization, activeVisualization); - - const { expression: workingExpression } = generateExpression({ - ...unappliedArgs, - datasourceMap, - errors: { - hasConfigurationValidationError: Boolean(configurationValidationError?.length), - hasMissingRefsErrors: Boolean(missingRefsErrors.length), - hasUnknownVisError: Boolean(unknownVisError), - }, - }); - return Boolean(workingExpression); -}; - -const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ - framePublicAPI, - visualizationMap, - datasourceMap, - core, - plugins, - ExpressionRenderer: ExpressionRendererComponent, - suggestionForDraggedField, - lensInspector, -}: Omit & { - suggestionForDraggedField: Suggestion | undefined; -}) { - const dispatchLens = useLensDispatch(); - const isFullscreen = useLensSelector(selectIsFullscreenDatasource); - - // The following variables shouldn't be used for logic in this component - // Use the non-underscore versions instead, because they will reflect the applied state - // even when the user has unapplied changes - const _visualization = useLensSelector(selectVisualization); - const _datasourceStates = useLensSelector(selectDatasourceStates); - const _activeDatasourceId = useLensSelector(selectActiveDatasourceId); - const _appliedState = useLensSelector(selectAppliedState); - - const { - visualization, - datasourceStates, - activeDatasourceId, - datasourceLayers, - activeVisualization, - } = getBuildExpressionArgs({ - visualization: _visualization, - datasourceStates: _datasourceStates, - activeDatasourceId: _activeDatasourceId, - appliedState: _appliedState, - visualizationMap, - framePublicAPI, - }); - - const [localState, setLocalState] = useState({ - expressionBuildError: undefined, - expandError: false, - }); + const unknownVisError = visualization.activeId && !activeVisualization; // Note: mind to all these eslint disable lines: the frameAPI will change too frequently // and to prevent race conditions it is ok to leave them there. const configurationValidationError = useMemo( () => - checkConfiguration({ + validateDatasourceAndVisualization( + activeDatasourceId ? datasourceMap[activeDatasourceId] : null, + activeDatasourceId && datasourceStates[activeDatasourceId]?.state, activeVisualization, - visualization, - activeDatasourceId, - datasourceStates, - datasourceMap, - datasourceLayers, - }), + visualization.state, + framePublicAPI + ), // eslint-disable-next-line react-hooks/exhaustive-deps [activeVisualization, visualization.state, activeDatasourceId, datasourceMap, datasourceStates] ); - const missingRefsErrors = checkMissingRefs({ - activeDatasourceId, - datasourceMap, - datasourceStates, - }); - const unknownVisError = checkUnknownVis(visualization, activeVisualization); - - const expression = useMemo(() => { - const { expression: _expression, expressionBuildError } = generateExpression({ - errors: { - hasConfigurationValidationError: Boolean(configurationValidationError?.length), - hasMissingRefsErrors: Boolean(missingRefsErrors?.length), - hasUnknownVisError: Boolean(unknownVisError), - }, - activeVisualization, - visualization, - datasourceMap, - datasourceStates, - datasourceLayers, - }); - - if (expressionBuildError) { - setLocalState((state) => ({ ...state, expressionBuildError })); - } + const _expression = useMemo(() => { + if (!configurationValidationError?.length && !missingRefsErrors.length && !unknownVisError) { + try { + const ast = buildExpression({ + visualization: activeVisualization, + visualizationState: visualization.state, + datasourceMap, + datasourceStates, + datasourceLayers, + }); - return _expression; + if (ast) { + // expression has to be turned into a string for dirty checking - if the ast is rebuilt, + // turning it into a string will make sure the expression renderer only re-renders if the + // expression actually changed. + return toExpression(ast); + } else { + return null; + } + } catch (e) { + const buildMessages = activeVisualization?.getErrorMessages(visualization.state); + const defaultMessage = { + shortMessage: i18n.translate('xpack.lens.editorFrame.buildExpressionError', { + defaultMessage: 'An unexpected error occurred while preparing the chart', + }), + longMessage: e.toString(), + }; + // Most likely an error in the expression provided by a datasource or visualization + setLocalState((s) => ({ + ...s, + expressionBuildError: buildMessages ?? [defaultMessage], + })); + } + } + if (unknownVisError) { + setLocalState((s) => ({ + ...s, + expressionBuildError: [getUnknownVisualizationTypeError(visualization.activeId!)], + })); + } }, [ - configurationValidationError?.length, - missingRefsErrors?.length, - unknownVisError, activeVisualization, - visualization, + visualization.state, datasourceMap, datasourceStates, datasourceLayers, + configurationValidationError?.length, + missingRefsErrors.length, + unknownVisError, + visualization.activeId, ]); - const expressionExists = Boolean(expression); - - const changesApplied = useLensSelector(selectChangesApplied); - - const isSaveable = useMemo(() => { - if (changesApplied) { - return expressionExists; - } else { - return areUnappliedChangesSaveable({ - _visualization, - _datasourceStates, - _activeDatasourceId, - visualizationMap, - datasourceMap, - framePublicAPI, - }); - } - }, [ - _activeDatasourceId, - _datasourceStates, - _visualization, - changesApplied, - datasourceMap, - expressionExists, - framePublicAPI, - visualizationMap, - ]); + if (shouldApplyExpression) { + expressionToRender = _expression; + } + + if (!autoApplyEnabled) { + dispatchLens(setChangesApplied(_expression === expressionToRender)); + } + + const expressionExists = Boolean(expressionToRender); + // null signals an empty workspace which should count as an initial render + if ((expressionExists || expressionToRender === null) && !localState.initialRenderComplete) { + setLocalState((s) => ({ ...s, initialRenderComplete: true })); + } useEffect(() => { - dispatchLens(setSaveable(isSaveable)); - }, [isSaveable, dispatchLens]); + dispatchLens(setSaveable(expressionExists)); + }, [expressionExists, dispatchLens]); const onEvent = useCallback( (event: ExpressionRendererEvent) => { @@ -572,12 +383,12 @@ const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ }; const renderVisualization = () => { - if (expression === null) { + if (expressionToRender === null) { return renderEmptyWorkspace(); } return ( ('lens/onAc export const setSaveable = createAction('lens/setSaveable'); export const enableAutoApply = createAction('lens/enableAutoApply'); export const disableAutoApply = createAction('lens/disableAutoApply'); -// this action is only relevant when auto-apply is disabled export const applyChanges = createAction('lens/applyChanges'); +export const setChangesApplied = createAction('lens/setChangesApplied'); export const updateState = createAction<{ updater: (prevState: LensAppState) => LensAppState; }>('lens/updateState'); @@ -169,6 +169,7 @@ export const lensActions = { enableAutoApply, disableAutoApply, applyChanges, + setChangesApplied, updateState, updateDatasourceState, updateVisualizationState, @@ -188,14 +189,6 @@ export const lensActions = { setLayerDefaultDimension, }; -const buildAppliedState = (state: LensAppState) => { - return { - activeDatasourceId: state.activeDatasourceId, - visualization: cloneDeep(state.visualization), - datasourceStates: cloneDeep(state.datasourceStates), - }; -}; - export const makeLensReducer = (storeDeps: LensStoreDeps) => { const { datasourceMap, visualizationMap } = storeDeps; return createReducer(initialState, { @@ -218,15 +211,19 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { }; }, [enableAutoApply.type]: (state) => { - delete state.appliedState; + state.autoApplyDisabled = false; }, [disableAutoApply.type]: (state) => { - state.appliedState = buildAppliedState(state); + state.autoApplyDisabled = true; }, [applyChanges.type]: (state) => { - if (state.appliedState) { - state.appliedState = buildAppliedState(state); + if (typeof state.applyChangesCounter === 'undefined') { + state.applyChangesCounter = 0; } + state.applyChangesCounter!++; + }, + [setChangesApplied.type]: (state, { payload: applied }) => { + state.changesApplied = applied; }, [updateState.type]: ( state, diff --git a/x-pack/plugins/lens/public/state_management/selectors.ts b/x-pack/plugins/lens/public/state_management/selectors.ts index a183197109739..3b6f9100bfaef 100644 --- a/x-pack/plugins/lens/public/state_management/selectors.ts +++ b/x-pack/plugins/lens/public/state_management/selectors.ts @@ -8,9 +8,8 @@ import { createSelector } from '@reduxjs/toolkit'; import { SavedObjectReference } from 'kibana/server'; import { FilterManager } from 'src/plugins/data/public'; -import { isEqual } from 'lodash'; import { LensState } from './types'; -import { Datasource, DatasourceMap, FramePublicAPI, VisualizationMap } from '../types'; +import { Datasource, DatasourceMap, VisualizationMap } from '../types'; import { getDatasourceLayers } from '../editor_frame_service/editor_frame'; export const selectPersistedDoc = (state: LensState) => state.lens.persistedDoc; @@ -20,28 +19,15 @@ export const selectFilters = (state: LensState) => state.lens.filters; export const selectResolvedDateRange = (state: LensState) => state.lens.resolvedDateRange; export const selectVisualization = (state: LensState) => state.lens.visualization; export const selectStagedPreview = (state: LensState) => state.lens.stagedPreview; -export const selectAppliedState = (state: LensState) => state.lens.appliedState; +export const selectAutoApplyEnabled = (state: LensState) => !state.lens.autoApplyDisabled; +export const selectChangesApplied = (state: LensState) => Boolean(state.lens.changesApplied); +export const selectApplyChangesCounter = (state: LensState) => state.lens.applyChangesCounter || 0; export const selectDatasourceStates = (state: LensState) => state.lens.datasourceStates; export const selectActiveDatasourceId = (state: LensState) => state.lens.activeDatasourceId; export const selectActiveData = (state: LensState) => state.lens.activeData; export const selectIsFullscreenDatasource = (state: LensState) => Boolean(state.lens.isFullscreenDatasource); -export const selectChangesApplied = createSelector( - [selectAppliedState, selectVisualization, selectDatasourceStates, selectActiveDatasourceId], - (appliedState, visualization, datasourceStates, activeDatasourceId) => { - if (!appliedState) return true; // auto-apply is enabled - - const workingState = { activeDatasourceId, visualization, datasourceStates }; - return isEqual(appliedState, workingState); - } -); - -export const selectAutoApplyEnabled = createSelector( - [selectAppliedState], - (appliedState) => !Boolean(appliedState) -); - export const selectExecutionContext = createSelector( [selectQuery, selectFilters, selectResolvedDateRange], (query, filters, dateRange) => ({ @@ -167,23 +153,13 @@ export const selectDatasourceLayers = createSelector( export const selectFramePublicAPI = createSelector( [ selectDatasourceStates, - selectAppliedState, selectActiveData, selectInjectedDependencies as SelectInjectedDependenciesFunction, ], - (datasourceStates, appliedState, activeData, datasourceMap) => { - const api: FramePublicAPI = { + (datasourceStates, activeData, datasourceMap) => { + return { datasourceLayers: getDatasourceLayers(datasourceStates, datasourceMap), activeData, }; - - if (appliedState) { - api.appliedDatasourceLayers = getDatasourceLayers( - appliedState.datasourceStates, - datasourceMap - ); - } - - return api; } ); diff --git a/x-pack/plugins/lens/public/state_management/types.ts b/x-pack/plugins/lens/public/state_management/types.ts index 38cc580cf525d..0c902f944072d 100644 --- a/x-pack/plugins/lens/public/state_management/types.ts +++ b/x-pack/plugins/lens/public/state_management/types.ts @@ -30,15 +30,12 @@ export interface PreviewState { visualization: VisualizationState; datasourceStates: DatasourceStates; } -export interface AppliedState { - activeDatasourceId: EditorFrameState['activeDatasourceId']; - visualization: VisualizationState; - datasourceStates: DatasourceStates; -} export interface EditorFrameState extends PreviewState { activeDatasourceId: string | null; stagedPreview?: PreviewState; - appliedState?: AppliedState; + autoApplyDisabled?: boolean; + applyChangesCounter?: number; + changesApplied?: boolean; isFullscreenDatasource?: boolean; } export interface LensAppState extends EditorFrameState {