mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Textbased] Lens integration - move updateAll
callback to middleware (#162165)
## Summary There are 2 things refactored in this PR: 1. To make the updates from the config panel update the chart in discover, we have to run the `onUpdateCb` function in all places where the state changes in Lens. The problem is that when user adds a new feature to Lens, this is a potential source of sync bugs. We cannot test this behaviour with the way it's written now to avoid these bugs. My approach here changes the updates to a running a custom middleware every time the store state updates. I had to exclude some initialization actions to not end up in infinite loop updates (there's probably a better approach instead of excluding I haven't thought of yet). Another argument to do it this way is a performance improvement inside Lens component where we had to sometimes get all the store to make an `onUpdateCb` call. 2. the `useChartConfigPanel` hook should not really be a hook but a component as it is a component (returns JSX.Element, displays UI based on props) so I refactored it to `ChartConfigPanel`. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
707ed134be
commit
95702ac644
28 changed files with 350 additions and 240 deletions
|
@ -33,7 +33,9 @@ export const unifiedHistogramServicesMock = {
|
|||
suggestions: jest.fn(() => allSuggestionsMock),
|
||||
};
|
||||
}),
|
||||
EditLensConfigPanelApi: jest.fn().mockResolvedValue(<span>Lens Config Panel Component</span>),
|
||||
EditLensConfigPanelApi: jest
|
||||
.fn()
|
||||
.mockResolvedValue(() => <span>Lens Config Panel Component</span>),
|
||||
},
|
||||
storage: {
|
||||
get: jest.fn(),
|
||||
|
|
|
@ -42,7 +42,7 @@ import { useTotalHits } from './hooks/use_total_hits';
|
|||
import { useRequestParams } from './hooks/use_request_params';
|
||||
import { useChartStyles } from './hooks/use_chart_styles';
|
||||
import { useChartActions } from './hooks/use_chart_actions';
|
||||
import { useChartConfigPanel } from './hooks/use_chart_config_panel';
|
||||
import { ChartConfigPanel } from './chart_config_panel';
|
||||
import { getLensAttributes } from './utils/get_lens_attributes';
|
||||
import { useRefetch } from './hooks/use_refetch';
|
||||
import { useEditVisualization } from './hooks/use_edit_visualization';
|
||||
|
@ -220,19 +220,6 @@ export function Chart({
|
|||
]
|
||||
);
|
||||
|
||||
const ChartConfigPanel = useChartConfigPanel({
|
||||
services,
|
||||
lensAttributesContext,
|
||||
dataView,
|
||||
lensTablesAdapter,
|
||||
currentSuggestion,
|
||||
isFlyoutVisible,
|
||||
setIsFlyoutVisible,
|
||||
isPlainRecord,
|
||||
query: originalQuery,
|
||||
onSuggestionChange,
|
||||
});
|
||||
|
||||
const onSuggestionSelectorChange = useCallback(
|
||||
(s: Suggestion | undefined) => {
|
||||
onSuggestionChange?.(s);
|
||||
|
@ -455,7 +442,22 @@ export function Chart({
|
|||
isSaveable={false}
|
||||
/>
|
||||
)}
|
||||
{isFlyoutVisible && ChartConfigPanel}
|
||||
{isFlyoutVisible && (
|
||||
<ChartConfigPanel
|
||||
{...{
|
||||
services,
|
||||
lensAttributesContext,
|
||||
dataView,
|
||||
lensTablesAdapter,
|
||||
currentSuggestion,
|
||||
isFlyoutVisible,
|
||||
setIsFlyoutVisible,
|
||||
isPlainRecord,
|
||||
query: originalQuery,
|
||||
onSuggestionChange,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import type { TypedLensByValueInput } from '@kbn/lens-plugin/public';
|
||||
import { render } from '@testing-library/react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { setTimeout } from 'timers/promises';
|
||||
import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield';
|
||||
import { unifiedHistogramServicesMock } from '../__mocks__/services';
|
||||
import { lensTablesAdapterMock } from '../__mocks__/lens_table_adapter';
|
||||
import { ChartConfigPanel } from './chart_config_panel';
|
||||
import type { LensAttributesContext } from './utils/get_lens_attributes';
|
||||
|
||||
describe('ChartConfigPanel', () => {
|
||||
it('should return a jsx element to edit the visualization', async () => {
|
||||
const lensAttributes = {
|
||||
visualizationType: 'lnsXY',
|
||||
title: 'test',
|
||||
} as TypedLensByValueInput['attributes'];
|
||||
const { container } = render(
|
||||
<ChartConfigPanel
|
||||
{...{
|
||||
services: unifiedHistogramServicesMock,
|
||||
dataView: dataViewWithTimefieldMock,
|
||||
lensAttributesContext: {
|
||||
attributes: lensAttributes,
|
||||
} as unknown as LensAttributesContext,
|
||||
isFlyoutVisible: true,
|
||||
setIsFlyoutVisible: jest.fn(),
|
||||
isPlainRecord: true,
|
||||
lensTablesAdapter: lensTablesAdapterMock,
|
||||
query: {
|
||||
sql: 'Select * from test',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
await act(() => setTimeout(0));
|
||||
expect(container).not.toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('should return null if not in text based mode', async () => {
|
||||
const lensAttributes = {
|
||||
visualizationType: 'lnsXY',
|
||||
title: 'test',
|
||||
} as TypedLensByValueInput['attributes'];
|
||||
const { container } = render(
|
||||
<ChartConfigPanel
|
||||
{...{
|
||||
services: unifiedHistogramServicesMock,
|
||||
dataView: dataViewWithTimefieldMock,
|
||||
lensAttributesContext: {
|
||||
attributes: lensAttributes,
|
||||
} as unknown as LensAttributesContext,
|
||||
isFlyoutVisible: true,
|
||||
setIsFlyoutVisible: jest.fn(),
|
||||
isPlainRecord: false,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
await act(() => setTimeout(0));
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
|
@ -12,10 +12,10 @@ import type { Suggestion } from '@kbn/lens-plugin/public';
|
|||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import type { Datatable } from '@kbn/expressions-plugin/common';
|
||||
|
||||
import type { UnifiedHistogramServices } from '../../types';
|
||||
import type { LensAttributesContext } from '../utils/get_lens_attributes';
|
||||
import type { UnifiedHistogramServices } from '../types';
|
||||
import type { LensAttributesContext } from './utils/get_lens_attributes';
|
||||
|
||||
export function useChartConfigPanel({
|
||||
export function ChartConfigPanel({
|
||||
services,
|
||||
lensAttributesContext,
|
||||
dataView,
|
||||
|
@ -49,7 +49,9 @@ export function useChartConfigPanel({
|
|||
...(datasourceState && { datasourceState }),
|
||||
...(visualizationState && { visualizationState }),
|
||||
} as Suggestion;
|
||||
onSuggestionChange?.(updatedSuggestion);
|
||||
if (!isEqual(updatedSuggestion, currentSuggestion)) {
|
||||
onSuggestionChange?.(updatedSuggestion);
|
||||
}
|
||||
},
|
||||
[currentSuggestion, onSuggestionChange]
|
||||
);
|
|
@ -1,66 +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 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 type { TypedLensByValueInput } from '@kbn/lens-plugin/public';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { act } from 'react-test-renderer';
|
||||
import { setTimeout } from 'timers/promises';
|
||||
import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield';
|
||||
import { unifiedHistogramServicesMock } from '../../__mocks__/services';
|
||||
import { lensTablesAdapterMock } from '../../__mocks__/lens_table_adapter';
|
||||
import { useChartConfigPanel } from './use_chart_config_panel';
|
||||
import type { LensAttributesContext } from '../utils/get_lens_attributes';
|
||||
|
||||
describe('useChartConfigPanel', () => {
|
||||
it('should return a jsx element to edit the visualization', async () => {
|
||||
const lensAttributes = {
|
||||
visualizationType: 'lnsXY',
|
||||
title: 'test',
|
||||
} as TypedLensByValueInput['attributes'];
|
||||
const hook = renderHook(() =>
|
||||
useChartConfigPanel({
|
||||
services: unifiedHistogramServicesMock,
|
||||
dataView: dataViewWithTimefieldMock,
|
||||
lensAttributesContext: {
|
||||
attributes: lensAttributes,
|
||||
} as unknown as LensAttributesContext,
|
||||
isFlyoutVisible: true,
|
||||
setIsFlyoutVisible: jest.fn(),
|
||||
isPlainRecord: true,
|
||||
lensTablesAdapter: lensTablesAdapterMock,
|
||||
query: {
|
||||
sql: 'Select * from test',
|
||||
},
|
||||
})
|
||||
);
|
||||
await act(() => setTimeout(0));
|
||||
expect(hook.result.current).toBeDefined();
|
||||
expect(hook.result.current).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should return null if not in text based mode', async () => {
|
||||
const lensAttributes = {
|
||||
visualizationType: 'lnsXY',
|
||||
title: 'test',
|
||||
} as TypedLensByValueInput['attributes'];
|
||||
const hook = renderHook(() =>
|
||||
useChartConfigPanel({
|
||||
services: unifiedHistogramServicesMock,
|
||||
dataView: dataViewWithTimefieldMock,
|
||||
lensAttributesContext: {
|
||||
attributes: lensAttributes,
|
||||
} as unknown as LensAttributesContext,
|
||||
isFlyoutVisible: true,
|
||||
setIsFlyoutVisible: jest.fn(),
|
||||
isPlainRecord: false,
|
||||
})
|
||||
);
|
||||
await act(() => setTimeout(0));
|
||||
expect(hook.result.current).toBeNull();
|
||||
});
|
||||
});
|
|
@ -26,7 +26,6 @@ import {
|
|||
useLensSelector,
|
||||
useLensDispatch,
|
||||
LensAppState,
|
||||
DispatchSetState,
|
||||
selectSavedObjectFormat,
|
||||
updateIndexPatterns,
|
||||
updateDatasourceState,
|
||||
|
@ -99,7 +98,7 @@ export function App({
|
|||
const saveAndExit = useRef<() => void>();
|
||||
|
||||
const dispatch = useLensDispatch();
|
||||
const dispatchSetState: DispatchSetState = useCallback(
|
||||
const dispatchSetState = useCallback(
|
||||
(state: Partial<LensAppState>) => dispatch(setState(state)),
|
||||
[dispatch]
|
||||
);
|
||||
|
|
|
@ -25,7 +25,6 @@ import {
|
|||
useLensSelector,
|
||||
useLensDispatch,
|
||||
LensAppState,
|
||||
DispatchSetState,
|
||||
switchAndCleanDatasource,
|
||||
} from '../state_management';
|
||||
import {
|
||||
|
@ -314,7 +313,7 @@ export const LensTopNavMenu = ({
|
|||
} = useLensSelector((state) => state.lens);
|
||||
|
||||
const dispatch = useLensDispatch();
|
||||
const dispatchSetState: DispatchSetState = React.useCallback(
|
||||
const dispatchSetState = React.useCallback(
|
||||
(state: Partial<LensAppState>) => dispatch(setState(state)),
|
||||
[dispatch]
|
||||
);
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import React, { FC, useCallback, useEffect, useState, useMemo } from 'react';
|
||||
import { PreloadedState } from '@reduxjs/toolkit';
|
||||
import { AppMountParameters, CoreSetup, CoreStart } from '@kbn/core/public';
|
||||
import { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
|
@ -48,10 +47,9 @@ import {
|
|||
navigateAway,
|
||||
LensRootStore,
|
||||
loadInitial,
|
||||
LensAppState,
|
||||
LensState,
|
||||
setState,
|
||||
} from '../state_management';
|
||||
import { getPreloadedState, setState } from '../state_management/lens_slice';
|
||||
import { getPreloadedState } from '../state_management/lens_slice';
|
||||
import { getLensInspectorService } from '../lens_inspector_service';
|
||||
import {
|
||||
LensAppLocator,
|
||||
|
@ -276,9 +274,7 @@ export async function mountApp(
|
|||
initialContext,
|
||||
initialStateFromLocator,
|
||||
};
|
||||
const lensStore: LensRootStore = makeConfigureStore(storeDeps, {
|
||||
lens: getPreloadedState(storeDeps) as LensAppState,
|
||||
} as unknown as PreloadedState<LensState>);
|
||||
const lensStore: LensRootStore = makeConfigureStore(storeDeps);
|
||||
|
||||
const EditorRenderer = React.memo(
|
||||
(props: { id?: string; history: History<unknown>; editByValue?: boolean }) => {
|
||||
|
@ -322,7 +318,7 @@ export async function mountApp(
|
|||
data.query.filterManager.setAppFilters([]);
|
||||
data.query.queryString.clearQuery();
|
||||
}
|
||||
lensStore.dispatch(setState(getPreloadedState(storeDeps) as LensAppState));
|
||||
lensStore.dispatch(setState(getPreloadedState(storeDeps)));
|
||||
lensStore.dispatch(loadInitial({ redirectCallback, initialInput, history: props.history }));
|
||||
}, [initialInput, props.history, redirectCallback]);
|
||||
useEffect(() => {
|
||||
|
|
|
@ -5,30 +5,23 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
import { EuiFlyout, EuiLoadingSpinner, EuiOverlayMask } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Provider } from 'react-redux';
|
||||
import { PreloadedState } from '@reduxjs/toolkit';
|
||||
import { 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 { isEqual } from 'lodash';
|
||||
import type { LensPluginStartDependencies } from '../../../plugin';
|
||||
import {
|
||||
makeConfigureStore,
|
||||
LensRootStore,
|
||||
LensAppState,
|
||||
LensState,
|
||||
loadInitial,
|
||||
} from '../../../state_management';
|
||||
import { getPreloadedState } from '../../../state_management/lens_slice';
|
||||
import { makeConfigureStore, LensRootStore, loadInitial } from '../../../state_management';
|
||||
import { generateId } from '../../../id_generator';
|
||||
import type { DatasourceMap, VisualizationMap } from '../../../types';
|
||||
import {
|
||||
LensEditConfigurationFlyout,
|
||||
type EditConfigPanelProps,
|
||||
} from './lens_configuration_flyout';
|
||||
import type { LensAppServices } from '../../types';
|
||||
|
||||
export type EditLensConfigurationProps = Omit<
|
||||
EditConfigPanelProps,
|
||||
|
@ -43,12 +36,39 @@ function LoadingSpinnerWithOverlay() {
|
|||
);
|
||||
}
|
||||
|
||||
export function getEditLensConfiguration(
|
||||
type UpdaterType = (datasourceState: unknown, visualizationState: unknown) => void;
|
||||
|
||||
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 (
|
||||
!isEqual(prevDatasourceStates, datasourceStates) ||
|
||||
!isEqual(prevVisualization, visualization) ||
|
||||
prevActiveDatasourceId !== activeDatasourceId
|
||||
) {
|
||||
updater(datasourceStates[activeDatasourceId].state, visualization.state);
|
||||
}
|
||||
};
|
||||
|
||||
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(coreStart, startDependencies)
|
||||
);
|
||||
|
||||
return ({
|
||||
attributes,
|
||||
dataView,
|
||||
|
@ -59,23 +79,6 @@ export function getEditLensConfiguration(
|
|||
adaptersTables,
|
||||
panelId,
|
||||
}: EditLensConfigurationProps) => {
|
||||
const [lensServices, setLensServices] = useState<LensAppServices>();
|
||||
useEffect(() => {
|
||||
async function loadLensService() {
|
||||
const { getLensServices, getLensAttributeService } = await import(
|
||||
'../../../async_services'
|
||||
);
|
||||
const lensServicesT = await getLensServices(
|
||||
coreStart,
|
||||
startDependencies,
|
||||
getLensAttributeService(coreStart, startDependencies)
|
||||
);
|
||||
|
||||
setLensServices(lensServicesT);
|
||||
}
|
||||
loadLensService();
|
||||
}, []);
|
||||
|
||||
if (!lensServices || !datasourceMap || !visualizationMap || !dataView.id) {
|
||||
return <LoadingSpinnerWithOverlay />;
|
||||
}
|
||||
|
@ -89,9 +92,11 @@ export function getEditLensConfiguration(
|
|||
? datasourceState.initialContext
|
||||
: undefined,
|
||||
};
|
||||
const lensStore: LensRootStore = makeConfigureStore(storeDeps, {
|
||||
lens: getPreloadedState(storeDeps) as LensAppState,
|
||||
} as unknown as PreloadedState<LensState>);
|
||||
const lensStore: LensRootStore = makeConfigureStore(
|
||||
storeDeps,
|
||||
undefined,
|
||||
updatingMiddleware(updateAll)
|
||||
);
|
||||
lensStore.dispatch(
|
||||
loadInitial({
|
||||
initialInput: {
|
||||
|
|
|
@ -93,7 +93,6 @@ export function LensEditConfigurationFlyout({
|
|||
dataViews: startDependencies.dataViews,
|
||||
uiActions: startDependencies.uiActions,
|
||||
hideLayerHeader: datasourceId === 'textBased',
|
||||
onUpdateStateCb: updateAll,
|
||||
};
|
||||
return (
|
||||
<>
|
||||
|
@ -120,7 +119,6 @@ export function LensEditConfigurationFlyout({
|
|||
<VisualizationToolbar
|
||||
activeVisualization={activeVisualization}
|
||||
framePublicAPI={framePublicAPI}
|
||||
onUpdateStateCb={updateAll}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<ConfigPanelWrapper {...layerPanelsProps} />
|
||||
|
|
|
@ -170,25 +170,18 @@ describe('ConfigPanel', () => {
|
|||
|
||||
it('updates datasources and visualizations', async () => {
|
||||
const props = getDefaultProps();
|
||||
const onUpdateCbSpy = jest.fn();
|
||||
const newProps = {
|
||||
...props,
|
||||
onUpdateStateCb: onUpdateCbSpy,
|
||||
};
|
||||
const { instance, lensStore } = await prepareAndMountComponent(newProps);
|
||||
const { instance, lensStore } = await prepareAndMountComponent(props);
|
||||
const { updateDatasource, updateAll } = instance.find(LayerPanel).props();
|
||||
|
||||
const newDatasourceState = 'updated';
|
||||
updateDatasource('testDatasource', newDatasourceState);
|
||||
await waitMs(0);
|
||||
expect(lensStore.dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(onUpdateCbSpy).toHaveBeenCalled();
|
||||
expect((lensStore.dispatch as jest.Mock).mock.calls[0][0].payload.newDatasourceState).toEqual(
|
||||
'updated'
|
||||
);
|
||||
|
||||
updateAll('testDatasource', newDatasourceState, props.visualizationState);
|
||||
expect(onUpdateCbSpy).toHaveBeenCalled();
|
||||
// wait for one tick so async updater has a chance to trigger
|
||||
await waitMs(0);
|
||||
expect(lensStore.dispatch).toHaveBeenCalledTimes(3);
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import React, { useMemo, memo, useCallback } from 'react';
|
||||
import { useStore } from 'react-redux';
|
||||
import { EuiForm } from '@elastic/eui';
|
||||
import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public';
|
||||
import { isOfAggregateQueryType } from '@kbn/es-query';
|
||||
|
@ -14,7 +13,7 @@ import {
|
|||
UPDATE_FILTER_REFERENCES_ACTION,
|
||||
UPDATE_FILTER_REFERENCES_TRIGGER,
|
||||
} from '@kbn/unified-search-plugin/public';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import { DragDropIdentifier, DropType } from '@kbn/dom-drag-drop';
|
||||
import {
|
||||
changeIndexPattern,
|
||||
|
@ -58,8 +57,7 @@ export function LayerPanels(
|
|||
activeVisualization: Visualization;
|
||||
}
|
||||
) {
|
||||
const lensStore = useStore();
|
||||
const { activeVisualization, datasourceMap, indexPatternService, onUpdateStateCb } = props;
|
||||
const { activeVisualization, datasourceMap, indexPatternService } = props;
|
||||
const { activeDatasourceId, visualization, datasourceStates, query } = useLensSelector(
|
||||
(state) => state.lens
|
||||
);
|
||||
|
@ -81,31 +79,19 @@ export function LayerPanels(
|
|||
newState,
|
||||
})
|
||||
);
|
||||
if (onUpdateStateCb && activeDatasourceId) {
|
||||
const dsState = datasourceStates[activeDatasourceId].state;
|
||||
onUpdateStateCb?.(dsState, newState);
|
||||
}
|
||||
},
|
||||
[activeDatasourceId, activeVisualization.id, datasourceStates, dispatchLens, onUpdateStateCb]
|
||||
[activeVisualization.id, dispatchLens]
|
||||
);
|
||||
const updateDatasource = useMemo(
|
||||
() =>
|
||||
(datasourceId: string | undefined, newState: unknown, dontSyncLinkedDimensions?: boolean) => {
|
||||
if (datasourceId) {
|
||||
const newDatasourceState =
|
||||
typeof newState === 'function'
|
||||
? newState(datasourceStates[datasourceId].state)
|
||||
: newState;
|
||||
|
||||
if (isEqual(newDatasourceState, datasourceStates[datasourceId].state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
onUpdateStateCb?.(newDatasourceState, visualization.state);
|
||||
|
||||
dispatchLens(
|
||||
updateDatasourceState({
|
||||
newDatasourceState,
|
||||
newDatasourceState:
|
||||
typeof newState === 'function'
|
||||
? newState(datasourceStates[datasourceId].state)
|
||||
: newState,
|
||||
datasourceId,
|
||||
clearStagedPreview: false,
|
||||
dontSyncLinkedDimensions,
|
||||
|
@ -113,7 +99,7 @@ export function LayerPanels(
|
|||
);
|
||||
}
|
||||
},
|
||||
[dispatchLens, onUpdateStateCb, visualization.state, datasourceStates]
|
||||
[dispatchLens, datasourceStates]
|
||||
);
|
||||
const updateDatasourceAsync = useMemo(
|
||||
() => (datasourceId: string | undefined, newState: unknown) => {
|
||||
|
@ -148,8 +134,6 @@ export function LayerPanels(
|
|||
? newVisualizationState(visualization.state)
|
||||
: newVisualizationState;
|
||||
|
||||
onUpdateStateCb?.(newDsState, newVisState);
|
||||
|
||||
dispatchLens(
|
||||
updateVisualizationState({
|
||||
visualizationId: activeVisualization.id,
|
||||
|
@ -166,7 +150,7 @@ export function LayerPanels(
|
|||
);
|
||||
}, 0);
|
||||
},
|
||||
[dispatchLens, onUpdateStateCb, visualization.state, datasourceStates, activeVisualization.id]
|
||||
[dispatchLens, visualization.state, datasourceStates, activeVisualization.id]
|
||||
);
|
||||
|
||||
const toggleFullscreen = useCallback(() => {
|
||||
|
@ -207,24 +191,15 @@ export function LayerPanels(
|
|||
layerIds,
|
||||
})
|
||||
);
|
||||
if (activeDatasourceId && onUpdateStateCb) {
|
||||
const newState = lensStore.getState().lens;
|
||||
onUpdateStateCb(
|
||||
newState.datasourceStates[activeDatasourceId].state,
|
||||
newState.visualization.state
|
||||
);
|
||||
}
|
||||
|
||||
removeLayerRef(layerToRemoveId);
|
||||
},
|
||||
[
|
||||
activeDatasourceId,
|
||||
activeVisualization.id,
|
||||
datasourceMap,
|
||||
datasourceStates,
|
||||
dispatchLens,
|
||||
layerIds,
|
||||
lensStore,
|
||||
onUpdateStateCb,
|
||||
props.framePublicAPI.datasourceLayers,
|
||||
props.uiActions,
|
||||
removeLayerRef,
|
||||
|
@ -267,13 +242,6 @@ export function LayerPanels(
|
|||
|
||||
dispatchLens(addLayerAction({ layerId, layerType, extraArg, ignoreInitialValues }));
|
||||
|
||||
if (activeDatasourceId && onUpdateStateCb) {
|
||||
const newState = lensStore.getState().lens;
|
||||
onUpdateStateCb(
|
||||
newState.datasourceStates[activeDatasourceId].state,
|
||||
newState.visualization.state
|
||||
);
|
||||
}
|
||||
setNextFocusedLayerId(layerId);
|
||||
};
|
||||
|
||||
|
@ -355,13 +323,6 @@ export function LayerPanels(
|
|||
const datasourcePublicAPI = props.framePublicAPI.datasourceLayers?.[layerId];
|
||||
const datasourceId = datasourcePublicAPI?.datasourceId;
|
||||
dispatchLens(removeDimension({ ...dimensionProps, datasourceId }));
|
||||
if (datasourceId && onUpdateStateCb) {
|
||||
const newState = lensStore.getState().lens;
|
||||
onUpdateStateCb(
|
||||
newState.datasourceStates[datasourceId].state,
|
||||
newState.visualization.state
|
||||
);
|
||||
}
|
||||
}}
|
||||
toggleFullscreen={toggleFullscreen}
|
||||
indexPatternService={indexPatternService}
|
||||
|
|
|
@ -29,7 +29,6 @@ export interface ConfigPanelWrapperProps {
|
|||
uiActions: UiActionsStart;
|
||||
getUserMessages?: UserMessagesGetter;
|
||||
hideLayerHeader?: boolean;
|
||||
onUpdateStateCb?: (datasourceState: unknown, visualizationState: unknown) => void;
|
||||
}
|
||||
|
||||
export interface LayerPanelProps {
|
||||
|
|
|
@ -383,15 +383,16 @@ describe('editor_frame', () => {
|
|||
hasDefaultTimeField: jest.fn(() => true),
|
||||
};
|
||||
mockDatasource.getPublicAPI.mockReturnValue(updatedPublicAPI);
|
||||
mockVisualization.getConfiguration.mockClear();
|
||||
|
||||
const setDatasourceState = (mockDatasource.DataPanelComponent as jest.Mock).mock.calls[0][0]
|
||||
.setState;
|
||||
act(() => {
|
||||
setDatasourceState({});
|
||||
setDatasourceState('newState');
|
||||
});
|
||||
|
||||
expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(3);
|
||||
expect(mockVisualization.getConfiguration).toHaveBeenLastCalledWith(
|
||||
expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(1);
|
||||
expect(mockVisualization.getConfiguration).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
frame: expect.objectContaining({
|
||||
datasourceLayers: {
|
||||
|
|
|
@ -798,7 +798,7 @@ describe('workspace_panel', () => {
|
|||
lensStore.dispatch(
|
||||
updateDatasourceState({
|
||||
datasourceId: 'testDatasource',
|
||||
newDatasourceState: {},
|
||||
newDatasourceState: 'newState',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
selectChangesApplied,
|
||||
applyChanges,
|
||||
selectAutoApplyEnabled,
|
||||
selectVisualizationState,
|
||||
} from '../../../state_management';
|
||||
import { WorkspaceTitle } from './title';
|
||||
import { LensInspector } from '../../../lens_inspector_service';
|
||||
|
@ -52,13 +53,10 @@ export interface WorkspacePanelWrapperProps {
|
|||
export function VisualizationToolbar(props: {
|
||||
activeVisualization: Visualization | null;
|
||||
framePublicAPI: FramePublicAPI;
|
||||
onUpdateStateCb?: (datasourceState: unknown, visualizationState: unknown) => void;
|
||||
}) {
|
||||
const dispatchLens = useLensDispatch();
|
||||
const { activeDatasourceId, visualization, datasourceStates } = useLensSelector(
|
||||
(state) => state.lens
|
||||
);
|
||||
const { activeVisualization, onUpdateStateCb } = props;
|
||||
const visualization = useLensSelector(selectVisualizationState);
|
||||
const { activeVisualization } = props;
|
||||
const setVisualizationState = useCallback(
|
||||
(newState: unknown) => {
|
||||
if (!activeVisualization) {
|
||||
|
@ -70,12 +68,8 @@ export function VisualizationToolbar(props: {
|
|||
newState,
|
||||
})
|
||||
);
|
||||
if (activeDatasourceId && onUpdateStateCb) {
|
||||
const dsState = datasourceStates[activeDatasourceId].state;
|
||||
onUpdateStateCb?.(dsState, newState);
|
||||
}
|
||||
},
|
||||
[activeDatasourceId, datasourceStates, dispatchLens, activeVisualization, onUpdateStateCb]
|
||||
[dispatchLens, activeVisualization]
|
||||
);
|
||||
|
||||
const ToolbarComponent = props.activeVisualization?.ToolbarComponent;
|
||||
|
|
|
@ -780,7 +780,7 @@ export class Embeddable
|
|||
|
||||
async openConfingPanel(startDependencies: LensPluginStartDependencies) {
|
||||
const { getEditLensConfiguration } = await import('../async_services');
|
||||
const Component = getEditLensConfiguration(
|
||||
const Component = await getEditLensConfiguration(
|
||||
this.deps.coreStart,
|
||||
startDependencies,
|
||||
this.deps.visualizationMap,
|
||||
|
|
|
@ -21,7 +21,9 @@ export const lensPluginMock = {
|
|||
SaveModalComponent: jest.fn(() => {
|
||||
return <span>Lens Save Modal Component</span>;
|
||||
}),
|
||||
EditLensConfigPanelApi: jest.fn().mockResolvedValue(<span>Lens Config Panel Component</span>),
|
||||
EditLensConfigPanelApi: jest
|
||||
.fn()
|
||||
.mockResolvedValue(() => <span>Lens Config Panel Component</span>),
|
||||
canUseEditor: jest.fn(() => true),
|
||||
navigateToPrefilledEditor: jest.fn(),
|
||||
getXyVisTypes: jest
|
||||
|
|
|
@ -677,7 +677,13 @@ export class LensPlugin {
|
|||
this.editorFrameService!.loadVisualizations(),
|
||||
this.editorFrameService!.loadDatasources(),
|
||||
]);
|
||||
return getEditLensConfiguration(core, startDependencies, visualizationMap, datasourceMap);
|
||||
const Component = await getEditLensConfiguration(
|
||||
core,
|
||||
startDependencies,
|
||||
visualizationMap,
|
||||
datasourceMap
|
||||
);
|
||||
return Component;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ describe('contextMiddleware', () => {
|
|||
});
|
||||
const { next, invoke, store } = createMiddleware(data);
|
||||
const action = {
|
||||
type: 'lens/setState',
|
||||
type: 'lens/setExecutionContext',
|
||||
payload: {
|
||||
visualization: {
|
||||
state: {},
|
||||
|
@ -74,7 +74,7 @@ describe('contextMiddleware', () => {
|
|||
},
|
||||
searchSessionId: 'sessionId-1',
|
||||
},
|
||||
type: 'lens/setState',
|
||||
type: 'lens/setExecutionContext',
|
||||
});
|
||||
expect(next).toHaveBeenCalledWith(action);
|
||||
});
|
||||
|
@ -92,7 +92,7 @@ describe('contextMiddleware', () => {
|
|||
});
|
||||
const { next, invoke, store } = createMiddleware(data);
|
||||
const action = {
|
||||
type: 'lens/setState',
|
||||
type: 'lens/setExecutionContext',
|
||||
payload: {
|
||||
visualization: {
|
||||
state: {},
|
||||
|
@ -109,7 +109,7 @@ describe('contextMiddleware', () => {
|
|||
},
|
||||
searchSessionId: 'sessionId-1',
|
||||
},
|
||||
type: 'lens/setState',
|
||||
type: 'lens/setExecutionContext',
|
||||
});
|
||||
expect(next).toHaveBeenCalledWith(action);
|
||||
});
|
||||
|
@ -136,7 +136,7 @@ describe('contextMiddleware', () => {
|
|||
|
||||
// setState shouldn't trigger
|
||||
const setStateAction = {
|
||||
type: 'lens/setState',
|
||||
type: 'lens/setExecutionContext',
|
||||
payload: {
|
||||
visualization: {
|
||||
state: {},
|
||||
|
@ -146,14 +146,14 @@ describe('contextMiddleware', () => {
|
|||
};
|
||||
invoke(setStateAction);
|
||||
expect(store.dispatch).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({ type: 'lens/setState' })
|
||||
expect.objectContaining({ type: 'lens/setExecutionContext' })
|
||||
);
|
||||
|
||||
// applyChanges should trigger
|
||||
const applyChangesAction = applyChanges();
|
||||
invoke(applyChangesAction);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ type: 'lens/setState' })
|
||||
expect.objectContaining({ type: 'lens/setExecutionContext' })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -171,7 +171,7 @@ describe('contextMiddleware', () => {
|
|||
});
|
||||
const { next, invoke, store } = createMiddleware(data);
|
||||
const action = {
|
||||
type: 'lens/setState',
|
||||
type: 'lens/setExecutionContext',
|
||||
payload: {
|
||||
visualization: {
|
||||
state: {},
|
||||
|
@ -196,7 +196,7 @@ describe('contextMiddleware', () => {
|
|||
});
|
||||
const { next, invoke, store } = createMiddleware(data);
|
||||
const action = {
|
||||
type: 'lens/setState',
|
||||
type: 'lens/setExecutionContext',
|
||||
payload: {
|
||||
visualization: {
|
||||
state: {},
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Dispatch, MiddlewareAPI, PayloadAction } from '@reduxjs/toolkit';
|
|||
import moment from 'moment';
|
||||
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import {
|
||||
setState,
|
||||
setExecutionContext,
|
||||
LensDispatch,
|
||||
LensStoreDeps,
|
||||
navigateAway,
|
||||
|
@ -93,7 +93,7 @@ function updateTimeRange(data: DataPublicPluginStart, dispatch: LensDispatch) {
|
|||
// if the lag is significant, start a new session to clear the cache
|
||||
if (nowDiff > Math.max(timeRangeLength * TIME_LAG_PERCENTAGE_LIMIT, TIME_LAG_MIN_LIMIT)) {
|
||||
dispatch(
|
||||
setState({
|
||||
setExecutionContext({
|
||||
searchSessionId: data.search.session.start(),
|
||||
resolvedDateRange: getResolvedDateRange(timefilter),
|
||||
})
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { delay, finalize, switchMap, tap } from 'rxjs/operators';
|
||||
import { debounce, isEqual } from 'lodash';
|
||||
import { waitUntilNextSessionCompletes$, DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import { setState, LensGetState, LensDispatch } from '..';
|
||||
import { setExecutionContext, LensGetState, LensDispatch } from '..';
|
||||
import { getResolvedDateRange } from '../../utils';
|
||||
|
||||
/**
|
||||
|
@ -21,14 +21,14 @@ export function subscribeToExternalContext(
|
|||
) {
|
||||
const { query: queryService, search } = data;
|
||||
const { filterManager } = queryService;
|
||||
|
||||
const dispatchFromExternal = (searchSessionId = search.session.start()) => {
|
||||
const globalFilters = filterManager.getFilters();
|
||||
const filters = isEqual(getState().lens.filters, globalFilters)
|
||||
? null
|
||||
: { filters: globalFilters };
|
||||
|
||||
dispatch(
|
||||
setState({
|
||||
setExecutionContext({
|
||||
searchSessionId,
|
||||
...filters,
|
||||
resolvedDateRange: getResolvedDateRange(queryService.timefilter.timefilter),
|
||||
|
|
86
x-pack/plugins/lens/public/state_management/index.test.ts
Normal file
86
x-pack/plugins/lens/public/state_management/index.test.ts
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 { Action, Dispatch, MiddlewareAPI } from '@reduxjs/toolkit';
|
||||
import { makeConfigureStore, onActiveDataChange, setExecutionContext } from '.';
|
||||
import { mockStoreDeps } from '../mocks';
|
||||
import { TableInspectorAdapter } from '../editor_frame_service/types';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
|
||||
describe('state management initialization and middlewares', () => {
|
||||
let store: ReturnType<typeof makeConfigureStore>;
|
||||
const updaterFn = jest.fn();
|
||||
const customMiddleware = jest.fn(
|
||||
(updater) => (_store: MiddlewareAPI) => (next: Dispatch) => (action: Action) => {
|
||||
next(action);
|
||||
updater(action);
|
||||
}
|
||||
);
|
||||
beforeEach(() => {
|
||||
store = makeConfigureStore(mockStoreDeps(), undefined, customMiddleware(updaterFn));
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('customMiddleware', () => {
|
||||
test('customMiddleware is initialized on store creation', () => {
|
||||
expect(customMiddleware).toHaveBeenCalled();
|
||||
expect(updaterFn).not.toHaveBeenCalled();
|
||||
});
|
||||
test('customMiddleware is run on action dispatch', () => {
|
||||
store.dispatch({ type: 'ANY_TYPE' });
|
||||
expect(updaterFn).toHaveBeenCalledWith({ type: 'ANY_TYPE' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('optimizingMiddleware', () => {
|
||||
test('state is updating when the activeData changes', () => {
|
||||
expect(store.getState().lens.activeData).toEqual(undefined);
|
||||
store.dispatch(
|
||||
onActiveDataChange({ activeData: { id: 1 } as unknown as TableInspectorAdapter })
|
||||
);
|
||||
expect(store.getState().lens.activeData).toEqual({ id: 1 });
|
||||
// this is a bit convoluted - we are checking that the updaterFn has been called because it's called (as the next middleware)
|
||||
// before the reducer function but we're actually interested in the reducer function being called that's further down the pipeline
|
||||
expect(updaterFn).toHaveBeenCalledTimes(1);
|
||||
store.dispatch(
|
||||
onActiveDataChange({ activeData: { id: 2 } as unknown as TableInspectorAdapter })
|
||||
);
|
||||
expect(store.getState().lens.activeData).toEqual({ id: 2 });
|
||||
expect(updaterFn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
test('state is not updating when the payload activeData is the same as in state', () => {
|
||||
store.dispatch(
|
||||
onActiveDataChange({ activeData: { id: 1 } as unknown as TableInspectorAdapter })
|
||||
);
|
||||
expect(store.getState().lens.activeData).toEqual({ id: 1 });
|
||||
expect(updaterFn).toHaveBeenCalledTimes(1);
|
||||
store.dispatch(
|
||||
onActiveDataChange({ activeData: { id: 1 } as unknown as TableInspectorAdapter })
|
||||
);
|
||||
expect(store.getState().lens.activeData).toEqual({ id: 1 });
|
||||
expect(updaterFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
test('state is updating when the execution context changes', () => {
|
||||
expect(store.getState().lens.filters).toEqual([]);
|
||||
expect(store.getState().lens.query).toEqual({ language: 'lucene', query: '' });
|
||||
expect(store.getState().lens.searchSessionId).toEqual('');
|
||||
store.dispatch(
|
||||
setExecutionContext({
|
||||
filters: ['filter'] as unknown as Filter[],
|
||||
query: { language: 'lucene', query: 'query' },
|
||||
searchSessionId: 'searchSessionId',
|
||||
})
|
||||
);
|
||||
expect(store.getState().lens.filters).toEqual(['filter']);
|
||||
expect(store.getState().lens.query).toEqual({ language: 'lucene', query: 'query' });
|
||||
expect(store.getState().lens.searchSessionId).toEqual('searchSessionId');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,10 +5,17 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { configureStore, getDefaultMiddleware, PreloadedState } from '@reduxjs/toolkit';
|
||||
import {
|
||||
configureStore,
|
||||
getDefaultMiddleware,
|
||||
PreloadedState,
|
||||
Action,
|
||||
Dispatch,
|
||||
MiddlewareAPI,
|
||||
} from '@reduxjs/toolkit';
|
||||
import { createLogger } from 'redux-logger';
|
||||
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
|
||||
import { makeLensReducer, lensActions } from './lens_slice';
|
||||
import { makeLensReducer, lensActions, getPreloadedState } from './lens_slice';
|
||||
import { LensState, LensStoreDeps } from './types';
|
||||
import { initMiddleware } from './init_middleware';
|
||||
import { optimizingMiddleware } from './optimizing_middleware';
|
||||
|
@ -19,7 +26,10 @@ export * from './selectors';
|
|||
|
||||
export const {
|
||||
loadInitial,
|
||||
initEmpty,
|
||||
initExisting,
|
||||
navigateAway,
|
||||
setExecutionContext,
|
||||
setState,
|
||||
enableAutoApply,
|
||||
disableAutoApply,
|
||||
|
@ -36,7 +46,6 @@ export const {
|
|||
switchAndCleanDatasource,
|
||||
updateIndexPatterns,
|
||||
setToggleFullscreen,
|
||||
initEmpty,
|
||||
editVisualizationAction,
|
||||
removeLayers,
|
||||
removeOrClearLayer,
|
||||
|
@ -50,9 +59,12 @@ export const {
|
|||
changeIndexPattern,
|
||||
} = lensActions;
|
||||
|
||||
type CustomMiddleware = (store: MiddlewareAPI) => (next: Dispatch) => (action: Action) => void;
|
||||
|
||||
export const makeConfigureStore = (
|
||||
storeDeps: LensStoreDeps,
|
||||
preloadedState: PreloadedState<LensState>
|
||||
preloadedState?: PreloadedState<LensState> | undefined,
|
||||
customMiddleware?: CustomMiddleware
|
||||
) => {
|
||||
const middleware = [
|
||||
...getDefaultMiddleware({
|
||||
|
@ -70,10 +82,13 @@ export const makeConfigureStore = (
|
|||
},
|
||||
}),
|
||||
initMiddleware(storeDeps),
|
||||
optimizingMiddleware(),
|
||||
contextMiddleware(storeDeps),
|
||||
fullscreenMiddleware(storeDeps),
|
||||
optimizingMiddleware(),
|
||||
];
|
||||
if (customMiddleware) {
|
||||
middleware.push(customMiddleware);
|
||||
}
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
middleware.push(
|
||||
createLogger({
|
||||
|
@ -88,7 +103,9 @@ export const makeConfigureStore = (
|
|||
lens: makeLensReducer(storeDeps),
|
||||
},
|
||||
middleware,
|
||||
preloadedState,
|
||||
preloadedState: preloadedState ?? {
|
||||
lens: getPreloadedState(storeDeps),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { cloneDeep } from 'lodash';
|
|||
import { MiddlewareAPI } from '@reduxjs/toolkit';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { History } from 'history';
|
||||
import { setState, initEmpty, LensStoreDeps } from '..';
|
||||
import { setState, initExisting, initEmpty, LensStoreDeps } from '..';
|
||||
import { disableAutoApply, getPreloadedState } from '../lens_slice';
|
||||
import { SharingSavedObjectProps } from '../../types';
|
||||
import { LensEmbeddableInput, LensByReferenceInput } from '../../embeddable/embeddable';
|
||||
|
@ -171,7 +171,7 @@ export function loadInitial(
|
|||
const currentSessionId =
|
||||
initialStateFromLocator?.searchSessionId || data.search.session.getSessionId();
|
||||
store.dispatch(
|
||||
setState({
|
||||
initExisting({
|
||||
isSaveable: true,
|
||||
filters: initialStateFromLocator.filters || data.query.filterManager.getFilters(),
|
||||
query: initialStateFromLocator.query || emptyState.query,
|
||||
|
@ -331,7 +331,7 @@ export function loadInitial(
|
|||
}) => {
|
||||
const currentSessionId = data.search.session.getSessionId();
|
||||
store.dispatch(
|
||||
setState({
|
||||
initExisting({
|
||||
isSaveable: true,
|
||||
sharingSavedObjectProps,
|
||||
filters: data.query.filterManager.getFilters(),
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { createAction, createReducer, current, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public';
|
||||
import { mapValues, uniq } from 'lodash';
|
||||
import { Query } from '@kbn/es-query';
|
||||
import { Filter, Query } from '@kbn/es-query';
|
||||
import { History } from 'history';
|
||||
import { LayerTypes } from '@kbn/expression-xy-plugin/public';
|
||||
import { EventAnnotationGroupConfig } from '@kbn/event-annotation-common';
|
||||
|
@ -27,7 +27,7 @@ import { getInitialDatasourceId, getResolvedDateRange, getRemoveOperation } from
|
|||
import type { DataViewsState, LensAppState, LensStoreDeps, VisualizationState } from './types';
|
||||
import type { Datasource, Visualization } from '../types';
|
||||
import { generateId } from '../id_generator';
|
||||
import type { LayerType } from '../../common/types';
|
||||
import type { DateRange, LayerType } from '../../common/types';
|
||||
import { getVisualizeFieldSuggestions } from '../editor_frame_service/editor_frame/suggestion_helpers';
|
||||
import type { FramePublicAPI, LensEditContextMapping, LensEditEvent } from '../types';
|
||||
import { selectDataViews, selectFramePublicAPI } from './selectors';
|
||||
|
@ -113,7 +113,7 @@ export const getPreloadedState = ({
|
|||
? data.query.queryString.getDefaultQuery()
|
||||
: getQueryFromContext(initialContext, data);
|
||||
|
||||
const state = {
|
||||
const state: LensAppState = {
|
||||
...initialState,
|
||||
isLoading: true,
|
||||
// Do not use app-specific filters from previous app,
|
||||
|
@ -124,7 +124,7 @@ export const getPreloadedState = ({
|
|||
: 'searchFilters' in initialContext && initialContext.searchFilters
|
||||
? initialContext.searchFilters
|
||||
: data.query.filterManager.getFilters(),
|
||||
searchSessionId: data.search.session.getSessionId(),
|
||||
searchSessionId: data.search.session.getSessionId() || '',
|
||||
resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter),
|
||||
isLinkedToOriginatingApp: Boolean(
|
||||
embeddableEditorIncomingState?.originatingApp ||
|
||||
|
@ -140,7 +140,18 @@ export const getPreloadedState = ({
|
|||
return state;
|
||||
};
|
||||
|
||||
export interface SetExecutionContextPayload {
|
||||
query?: Query;
|
||||
filters?: Filter[];
|
||||
searchSessionId?: string;
|
||||
resolvedDateRange?: DateRange;
|
||||
}
|
||||
|
||||
export const setState = createAction<Partial<LensAppState>>('lens/setState');
|
||||
export const setExecutionContext = createAction<SetExecutionContextPayload>(
|
||||
'lens/setExecutionContext'
|
||||
);
|
||||
export const initExisting = createAction<Partial<LensAppState>>('lens/initExisting');
|
||||
export const onActiveDataChange = createAction<{
|
||||
activeData: TableInspectorAdapter;
|
||||
}>('lens/onActiveDataChange');
|
||||
|
@ -268,7 +279,9 @@ export const registerLibraryAnnotationGroup = createAction<{
|
|||
}>('lens/registerLibraryAnnotationGroup');
|
||||
|
||||
export const lensActions = {
|
||||
initExisting,
|
||||
setState,
|
||||
setExecutionContext,
|
||||
onActiveDataChange,
|
||||
setSaveable,
|
||||
enableAutoApply,
|
||||
|
@ -312,6 +325,18 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => {
|
|||
...payload,
|
||||
};
|
||||
},
|
||||
[setExecutionContext.type]: (state, { payload }: PayloadAction<SetExecutionContextPayload>) => {
|
||||
return {
|
||||
...state,
|
||||
...payload,
|
||||
};
|
||||
},
|
||||
[initExisting.type]: (state, { payload }: PayloadAction<Partial<LensAppState>>) => {
|
||||
return {
|
||||
...state,
|
||||
...payload,
|
||||
};
|
||||
},
|
||||
[onActiveDataChange.type]: (
|
||||
state,
|
||||
{ payload: { activeData } }: PayloadAction<{ activeData: TableInspectorAdapter }>
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
|
||||
import { Dispatch, MiddlewareAPI, Action } from '@reduxjs/toolkit';
|
||||
import { isEqual } from 'lodash';
|
||||
import { onActiveDataChange } from '.';
|
||||
import { onActiveDataChange, updateDatasourceState, setExecutionContext } from '.';
|
||||
import { SetExecutionContextPayload } from './lens_slice';
|
||||
|
||||
/** cancels updates to the store that don't change the state */
|
||||
export const optimizingMiddleware = () => (store: MiddlewareAPI) => {
|
||||
|
@ -16,7 +17,30 @@ export const optimizingMiddleware = () => (store: MiddlewareAPI) => {
|
|||
if (isEqual(store.getState().lens.activeData, action.payload.activeData)) {
|
||||
return;
|
||||
}
|
||||
} else if (updateDatasourceState.match(action)) {
|
||||
const { datasourceId, newDatasourceState } = action.payload;
|
||||
const { datasourceStates } = store.getState().lens;
|
||||
if (isEqual(datasourceStates[datasourceId].state, newDatasourceState)) {
|
||||
return;
|
||||
}
|
||||
} else if (setExecutionContext.match(action)) {
|
||||
const payloadKeys = Object.keys(action.payload);
|
||||
const prevState = store.getState().lens;
|
||||
const stateSliceToUpdate = payloadKeys.reduce<SetExecutionContextPayload>(
|
||||
(acc, currentKey) => {
|
||||
return {
|
||||
...acc,
|
||||
[currentKey]: prevState[currentKey],
|
||||
};
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
if (isEqual(action.payload, stateSliceToUpdate)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
next(action);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -67,11 +67,6 @@ export interface LensAppState extends EditorFrameState {
|
|||
annotationGroups: AnnotationGroups;
|
||||
}
|
||||
|
||||
export type DispatchSetState = (state: Partial<LensAppState>) => {
|
||||
payload: Partial<LensAppState>;
|
||||
type: string;
|
||||
};
|
||||
|
||||
export interface LensState {
|
||||
lens: LensAppState;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue