mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Lens] moving store loading to middleware (#106872)
This commit is contained in:
parent
91e64e0afa
commit
bcb16c1b86
25 changed files with 506 additions and 436 deletions
|
@ -70,6 +70,7 @@ const sessionIdSubject = new Subject<string>();
|
|||
describe('Lens App', () => {
|
||||
let defaultDoc: Document;
|
||||
let defaultSavedObjectId: string;
|
||||
|
||||
const mockDatasource: DatasourceMock = createMockDatasource('testDatasource');
|
||||
const mockDatasource2: DatasourceMock = createMockDatasource('testDatasource2');
|
||||
const datasourceMap = {
|
||||
|
|
|
@ -6,24 +6,20 @@
|
|||
*/
|
||||
|
||||
import React, { FC, useCallback } from 'react';
|
||||
|
||||
import { DeepPartial } from '@reduxjs/toolkit';
|
||||
import { AppMountParameters, CoreSetup, CoreStart } from 'kibana/public';
|
||||
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
|
||||
import { HashRouter, Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||
import { History } from 'history';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { DashboardFeatureFlagConfig } from 'src/plugins/dashboard/public';
|
||||
import { Provider } from 'react-redux';
|
||||
import { isEqual } from 'lodash';
|
||||
import { EmbeddableEditorState } from 'src/plugins/embeddable/public';
|
||||
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
|
||||
|
||||
import { LensReportManager, setReportManager, trackUiEvent } from '../lens_ui_telemetry';
|
||||
|
||||
import { App } from './app';
|
||||
import { Datasource, EditorFrameStart, Visualization } from '../types';
|
||||
import { EditorFrameStart } from '../types';
|
||||
import { addHelpMenuToAppChrome } from '../help_menu_util';
|
||||
import { LensPluginStartDependencies } from '../plugin';
|
||||
import { LENS_EMBEDDABLE_TYPE, LENS_EDIT_BY_VALUE, APP_ID } from '../../common';
|
||||
|
@ -32,32 +28,18 @@ import {
|
|||
LensByReferenceInput,
|
||||
LensByValueInput,
|
||||
} from '../embeddable/embeddable';
|
||||
import {
|
||||
ACTION_VISUALIZE_LENS_FIELD,
|
||||
VisualizeFieldContext,
|
||||
} from '../../../../../src/plugins/ui_actions/public';
|
||||
import { ACTION_VISUALIZE_LENS_FIELD } from '../../../../../src/plugins/ui_actions/public';
|
||||
import { LensAttributeService } from '../lens_attribute_service';
|
||||
import { LensAppServices, RedirectToOriginProps, HistoryLocationState } from './types';
|
||||
import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
import {
|
||||
makeConfigureStore,
|
||||
navigateAway,
|
||||
getPreloadedState,
|
||||
LensRootStore,
|
||||
setState,
|
||||
LensAppState,
|
||||
updateLayer,
|
||||
updateVisualizationState,
|
||||
loadInitial,
|
||||
LensState,
|
||||
} from '../state_management';
|
||||
import { getPersistedDoc } from './save_modal_container';
|
||||
import { getResolvedDateRange, getInitialDatasourceId } from '../utils';
|
||||
import { initializeDatasources } from '../editor_frame_service/editor_frame';
|
||||
import { generateId } from '../id_generator';
|
||||
import {
|
||||
getVisualizeFieldSuggestions,
|
||||
switchToSuggestion,
|
||||
} from '../editor_frame_service/editor_frame/suggestion_helpers';
|
||||
import { getPreloadedState } from '../state_management/lens_slice';
|
||||
|
||||
export async function getLensServices(
|
||||
coreStart: CoreStart,
|
||||
|
@ -114,7 +96,7 @@ export async function mountApp(
|
|||
|
||||
const lensServices = await getLensServices(coreStart, startDependencies, attributeService);
|
||||
|
||||
const { stateTransfer, data, storage, dashboardFeatureFlag } = lensServices;
|
||||
const { stateTransfer, data, storage } = lensServices;
|
||||
|
||||
const embeddableEditorIncomingState = stateTransfer?.getIncomingEditorState(APP_ID);
|
||||
|
||||
|
@ -183,37 +165,19 @@ export async function mountApp(
|
|||
if (embeddableEditorIncomingState?.searchSessionId) {
|
||||
data.search.session.continue(embeddableEditorIncomingState.searchSessionId);
|
||||
}
|
||||
|
||||
const { datasourceMap, visualizationMap } = instance;
|
||||
const storeDeps = {
|
||||
lensServices,
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
embeddableEditorIncomingState,
|
||||
initialContext,
|
||||
};
|
||||
const lensStore: LensRootStore = makeConfigureStore(storeDeps, {
|
||||
lens: getPreloadedState(storeDeps),
|
||||
} as DeepPartial<LensState>);
|
||||
|
||||
const initialDatasourceId = getInitialDatasourceId(datasourceMap);
|
||||
const datasourceStates: LensAppState['datasourceStates'] = {};
|
||||
if (initialDatasourceId) {
|
||||
datasourceStates[initialDatasourceId] = {
|
||||
state: null,
|
||||
isLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
const preloadedState = getPreloadedState({
|
||||
isLoading: true,
|
||||
query: data.query.queryString.getQuery(),
|
||||
// Do not use app-specific filters from previous app,
|
||||
// only if Lens was opened with the intention to visualize a field (e.g. coming from Discover)
|
||||
filters: !initialContext
|
||||
? data.query.filterManager.getGlobalFilters()
|
||||
: data.query.filterManager.getFilters(),
|
||||
searchSessionId: data.search.session.getSessionId(),
|
||||
resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter),
|
||||
isLinkedToOriginatingApp: Boolean(embeddableEditorIncomingState?.originatingApp),
|
||||
activeDatasourceId: initialDatasourceId,
|
||||
datasourceStates,
|
||||
visualization: {
|
||||
state: null,
|
||||
activeId: Object.keys(visualizationMap)[0] || null,
|
||||
},
|
||||
});
|
||||
|
||||
const lensStore: LensRootStore = makeConfigureStore(preloadedState, { data });
|
||||
const EditorRenderer = React.memo(
|
||||
(props: { id?: string; history: History<unknown>; editByValue?: boolean }) => {
|
||||
const redirectCallback = useCallback(
|
||||
|
@ -224,17 +188,7 @@ export async function mountApp(
|
|||
);
|
||||
trackUiEvent('loaded');
|
||||
const initialInput = getInitialInput(props.id, props.editByValue);
|
||||
loadInitialStore(
|
||||
redirectCallback,
|
||||
initialInput,
|
||||
lensServices,
|
||||
lensStore,
|
||||
embeddableEditorIncomingState,
|
||||
dashboardFeatureFlag,
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
initialContext
|
||||
);
|
||||
lensStore.dispatch(loadInitial({ redirectCallback, initialInput }));
|
||||
|
||||
return (
|
||||
<Provider store={lensStore}>
|
||||
|
@ -309,181 +263,3 @@ export async function mountApp(
|
|||
lensStore.dispatch(navigateAway());
|
||||
};
|
||||
}
|
||||
|
||||
export function loadInitialStore(
|
||||
redirectCallback: (savedObjectId?: string) => void,
|
||||
initialInput: LensEmbeddableInput | undefined,
|
||||
lensServices: LensAppServices,
|
||||
lensStore: LensRootStore,
|
||||
embeddableEditorIncomingState: EmbeddableEditorState | undefined,
|
||||
dashboardFeatureFlag: DashboardFeatureFlagConfig,
|
||||
datasourceMap: Record<string, Datasource>,
|
||||
visualizationMap: Record<string, Visualization>,
|
||||
initialContext?: VisualizeFieldContext
|
||||
) {
|
||||
const { attributeService, chrome, notifications, data } = lensServices;
|
||||
const { persistedDoc } = lensStore.getState().lens;
|
||||
if (
|
||||
!initialInput ||
|
||||
(attributeService.inputIsRefType(initialInput) &&
|
||||
initialInput.savedObjectId === persistedDoc?.savedObjectId)
|
||||
) {
|
||||
return initializeDatasources(
|
||||
datasourceMap,
|
||||
lensStore.getState().lens.datasourceStates,
|
||||
undefined,
|
||||
initialContext,
|
||||
{
|
||||
isFullEditor: true,
|
||||
}
|
||||
)
|
||||
.then((result) => {
|
||||
const datasourceStates = Object.entries(result).reduce(
|
||||
(state, [datasourceId, datasourceState]) => ({
|
||||
...state,
|
||||
[datasourceId]: {
|
||||
...datasourceState,
|
||||
isLoading: false,
|
||||
},
|
||||
}),
|
||||
{}
|
||||
);
|
||||
lensStore.dispatch(
|
||||
setState({
|
||||
datasourceStates,
|
||||
isLoading: false,
|
||||
})
|
||||
);
|
||||
if (initialContext) {
|
||||
const selectedSuggestion = getVisualizeFieldSuggestions({
|
||||
datasourceMap,
|
||||
datasourceStates,
|
||||
visualizationMap,
|
||||
activeVisualizationId: Object.keys(visualizationMap)[0] || null,
|
||||
visualizationState: null,
|
||||
visualizeTriggerFieldContext: initialContext,
|
||||
});
|
||||
if (selectedSuggestion) {
|
||||
switchToSuggestion(lensStore.dispatch, selectedSuggestion, 'SWITCH_VISUALIZATION');
|
||||
}
|
||||
}
|
||||
const activeDatasourceId = getInitialDatasourceId(datasourceMap);
|
||||
const visualization = lensStore.getState().lens.visualization;
|
||||
const activeVisualization =
|
||||
visualization.activeId && visualizationMap[visualization.activeId];
|
||||
|
||||
if (visualization.state === null && activeVisualization) {
|
||||
const newLayerId = generateId();
|
||||
|
||||
const initialVisualizationState = activeVisualization.initialize(() => newLayerId);
|
||||
lensStore.dispatch(
|
||||
updateLayer({
|
||||
datasourceId: activeDatasourceId!,
|
||||
layerId: newLayerId,
|
||||
updater: datasourceMap[activeDatasourceId!].insertLayer,
|
||||
})
|
||||
);
|
||||
lensStore.dispatch(
|
||||
updateVisualizationState({
|
||||
visualizationId: activeVisualization.id,
|
||||
updater: initialVisualizationState,
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((e: { message: string }) => {
|
||||
notifications.toasts.addDanger({
|
||||
title: e.message,
|
||||
});
|
||||
redirectCallback();
|
||||
});
|
||||
}
|
||||
|
||||
getPersistedDoc({
|
||||
initialInput,
|
||||
attributeService,
|
||||
data,
|
||||
chrome,
|
||||
notifications,
|
||||
})
|
||||
.then(
|
||||
(doc) => {
|
||||
if (doc) {
|
||||
const currentSessionId = data.search.session.getSessionId();
|
||||
const docDatasourceStates = Object.entries(doc.state.datasourceStates).reduce(
|
||||
(stateMap, [datasourceId, datasourceState]) => ({
|
||||
...stateMap,
|
||||
[datasourceId]: {
|
||||
isLoading: true,
|
||||
state: datasourceState,
|
||||
},
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
initializeDatasources(
|
||||
datasourceMap,
|
||||
docDatasourceStates,
|
||||
doc.references,
|
||||
initialContext,
|
||||
{
|
||||
isFullEditor: true,
|
||||
}
|
||||
)
|
||||
.then((result) => {
|
||||
const activeDatasourceId = getInitialDatasourceId(datasourceMap, doc);
|
||||
|
||||
lensStore.dispatch(
|
||||
setState({
|
||||
query: doc.state.query,
|
||||
searchSessionId:
|
||||
dashboardFeatureFlag.allowByValueEmbeddables &&
|
||||
Boolean(embeddableEditorIncomingState?.originatingApp) &&
|
||||
!(initialInput as LensByReferenceInput)?.savedObjectId &&
|
||||
currentSessionId
|
||||
? currentSessionId
|
||||
: data.search.session.start(),
|
||||
...(!isEqual(persistedDoc, doc) ? { persistedDoc: doc } : null),
|
||||
activeDatasourceId,
|
||||
visualization: {
|
||||
activeId: doc.visualizationType,
|
||||
state: doc.state.visualization,
|
||||
},
|
||||
datasourceStates: Object.entries(result).reduce(
|
||||
(state, [datasourceId, datasourceState]) => ({
|
||||
...state,
|
||||
[datasourceId]: {
|
||||
...datasourceState,
|
||||
isLoading: false,
|
||||
},
|
||||
}),
|
||||
{}
|
||||
),
|
||||
isLoading: false,
|
||||
})
|
||||
);
|
||||
})
|
||||
.catch((e: { message: string }) =>
|
||||
notifications.toasts.addDanger({
|
||||
title: e.message,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
redirectCallback();
|
||||
}
|
||||
},
|
||||
() => {
|
||||
lensStore.dispatch(
|
||||
setState({
|
||||
isLoading: false,
|
||||
})
|
||||
);
|
||||
redirectCallback();
|
||||
}
|
||||
)
|
||||
.catch((e: { message: string }) =>
|
||||
notifications.toasts.addDanger({
|
||||
title: e.message,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ import {
|
|||
EmbeddableEditorState,
|
||||
EmbeddableStateTransfer,
|
||||
} from '../../../../../src/plugins/embeddable/public';
|
||||
import { Datasource, EditorFrameInstance, Visualization } from '../types';
|
||||
import { DatasourceMap, EditorFrameInstance, VisualizationMap } from '../types';
|
||||
import { PresentationUtilPluginStart } from '../../../../../src/plugins/presentation_util/public';
|
||||
export interface RedirectToOriginProps {
|
||||
input?: LensEmbeddableInput;
|
||||
|
@ -54,8 +54,8 @@ export interface LensAppProps {
|
|||
|
||||
// State passed in by the container which is used to determine the id of the Originating App.
|
||||
incomingState?: EmbeddableEditorState;
|
||||
datasourceMap: Record<string, Datasource>;
|
||||
visualizationMap: Record<string, Visualization>;
|
||||
datasourceMap: DatasourceMap;
|
||||
visualizationMap: VisualizationMap;
|
||||
}
|
||||
|
||||
export type RunSave = (
|
||||
|
@ -82,7 +82,7 @@ export interface LensTopNavMenuProps {
|
|||
indicateNoData: boolean;
|
||||
setIsSaveModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
runSave: RunSave;
|
||||
datasourceMap: Record<string, Datasource>;
|
||||
datasourceMap: DatasourceMap;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,11 +27,8 @@ import {
|
|||
} from '../../../state_management';
|
||||
|
||||
export const ConfigPanelWrapper = memo(function ConfigPanelWrapper(props: ConfigPanelWrapperProps) {
|
||||
const activeVisualization = props.visualizationMap[props.activeVisualizationId || ''];
|
||||
const { visualizationState } = props;
|
||||
|
||||
return activeVisualization && visualizationState ? (
|
||||
<LayerPanels {...props} activeVisualization={activeVisualization} />
|
||||
return props.activeVisualization && props.visualizationState ? (
|
||||
<LayerPanels {...props} activeVisualization={props.activeVisualization} />
|
||||
) : null;
|
||||
});
|
||||
|
||||
|
|
|
@ -8,17 +8,16 @@
|
|||
import {
|
||||
Visualization,
|
||||
FramePublicAPI,
|
||||
Datasource,
|
||||
DatasourceDimensionEditorProps,
|
||||
VisualizationDimensionGroupConfig,
|
||||
DatasourceMap,
|
||||
} from '../../../types';
|
||||
export interface ConfigPanelWrapperProps {
|
||||
activeDatasourceId: string;
|
||||
visualizationState: unknown;
|
||||
visualizationMap: Record<string, Visualization>;
|
||||
activeVisualizationId: string | null;
|
||||
activeVisualization: Visualization | null;
|
||||
framePublicAPI: FramePublicAPI;
|
||||
datasourceMap: Record<string, Datasource>;
|
||||
datasourceMap: DatasourceMap;
|
||||
datasourceStates: Record<
|
||||
string,
|
||||
{
|
||||
|
@ -33,7 +32,7 @@ export interface ConfigPanelWrapperProps {
|
|||
export interface LayerPanelProps {
|
||||
activeDatasourceId: string;
|
||||
visualizationState: unknown;
|
||||
datasourceMap: Record<string, Datasource>;
|
||||
datasourceMap: DatasourceMap;
|
||||
activeVisualization: Visualization;
|
||||
framePublicAPI: FramePublicAPI;
|
||||
datasourceStates: Record<
|
||||
|
|
|
@ -13,7 +13,7 @@ import { EuiPopover, EuiButtonIcon, EuiContextMenuPanel, EuiContextMenuItem } fr
|
|||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { NativeRenderer } from '../../native_renderer';
|
||||
import { DragContext, DragDropIdentifier } from '../../drag_drop';
|
||||
import { StateSetter, DatasourceDataPanelProps, Datasource } from '../../types';
|
||||
import { StateSetter, DatasourceDataPanelProps, DatasourceMap } from '../../types';
|
||||
import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public';
|
||||
import {
|
||||
switchDatasource,
|
||||
|
@ -27,7 +27,7 @@ import { initializeDatasources } from './state_helpers';
|
|||
|
||||
interface DataPanelWrapperProps {
|
||||
datasourceState: unknown;
|
||||
datasourceMap: Record<string, Datasource>;
|
||||
datasourceMap: DatasourceMap;
|
||||
activeDatasource: string | null;
|
||||
datasourceIsLoading: boolean;
|
||||
showNoDataPopover: () => void;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React, { useCallback, useRef, useMemo } from 'react';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import { ReactExpressionRendererType } from '../../../../../../src/plugins/expressions/public';
|
||||
import { Datasource, FramePublicAPI, Visualization } from '../../types';
|
||||
import { DatasourceMap, FramePublicAPI, VisualizationMap } from '../../types';
|
||||
import { DataPanelWrapper } from './data_panel_wrapper';
|
||||
import { ConfigPanelWrapper } from './config_panel';
|
||||
import { FrameLayout } from './frame_layout';
|
||||
|
@ -22,8 +22,8 @@ import { trackUiEvent } from '../../lens_ui_telemetry';
|
|||
import { useLensSelector, useLensDispatch } from '../../state_management';
|
||||
|
||||
export interface EditorFrameProps {
|
||||
datasourceMap: Record<string, Datasource>;
|
||||
visualizationMap: Record<string, Visualization>;
|
||||
datasourceMap: DatasourceMap;
|
||||
visualizationMap: VisualizationMap;
|
||||
ExpressionRenderer: ReactExpressionRendererType;
|
||||
core: CoreStart;
|
||||
plugins: EditorFrameStartPlugins;
|
||||
|
@ -125,11 +125,12 @@ export function EditorFrame(props: EditorFrameProps) {
|
|||
configPanel={
|
||||
allLoaded && (
|
||||
<ConfigPanelWrapper
|
||||
activeVisualization={
|
||||
visualization.activeId ? props.visualizationMap[visualization.activeId] : null
|
||||
}
|
||||
activeDatasourceId={activeDatasourceId!}
|
||||
datasourceMap={props.datasourceMap}
|
||||
datasourceStates={datasourceStates}
|
||||
visualizationMap={props.visualizationMap}
|
||||
activeVisualizationId={visualization.activeId}
|
||||
visualizationState={visualization.state}
|
||||
framePublicAPI={framePublicAPI}
|
||||
core={props.core}
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
*/
|
||||
|
||||
import { Ast, fromExpression, ExpressionFunctionAST } from '@kbn/interpreter/common';
|
||||
import { Visualization, Datasource, DatasourcePublicAPI } from '../../types';
|
||||
import { Visualization, DatasourcePublicAPI, DatasourceMap } from '../../types';
|
||||
|
||||
export function prependDatasourceExpression(
|
||||
visualizationExpression: Ast | string | null,
|
||||
datasourceMap: Record<string, Datasource>,
|
||||
datasourceMap: DatasourceMap,
|
||||
datasourceStates: Record<
|
||||
string,
|
||||
{
|
||||
|
@ -80,7 +80,7 @@ export function buildExpression({
|
|||
description?: string;
|
||||
visualization: Visualization | null;
|
||||
visualizationState: unknown;
|
||||
datasourceMap: Record<string, Datasource>;
|
||||
datasourceMap: DatasourceMap;
|
||||
datasourceStates: Record<
|
||||
string,
|
||||
{
|
||||
|
|
|
@ -10,11 +10,13 @@ import { Ast } from '@kbn/interpreter/common';
|
|||
import memoizeOne from 'memoize-one';
|
||||
import {
|
||||
Datasource,
|
||||
DatasourceMap,
|
||||
DatasourcePublicAPI,
|
||||
FramePublicAPI,
|
||||
InitializationOptions,
|
||||
Visualization,
|
||||
VisualizationDimensionGroupConfig,
|
||||
VisualizationMap,
|
||||
} from '../../types';
|
||||
import { buildExpression } from './expression_helpers';
|
||||
import { Document } from '../../persistence/saved_object_store';
|
||||
|
@ -28,7 +30,7 @@ import {
|
|||
} from '../error_helper';
|
||||
|
||||
export async function initializeDatasources(
|
||||
datasourceMap: Record<string, Datasource>,
|
||||
datasourceMap: DatasourceMap,
|
||||
datasourceStates: Record<string, { state: unknown; isLoading: boolean }>,
|
||||
references?: SavedObjectReference[],
|
||||
initialContext?: VisualizeFieldContext,
|
||||
|
@ -55,7 +57,7 @@ export async function initializeDatasources(
|
|||
}
|
||||
|
||||
export const createDatasourceLayers = memoizeOne(function createDatasourceLayers(
|
||||
datasourceMap: Record<string, Datasource>,
|
||||
datasourceMap: DatasourceMap,
|
||||
datasourceStates: Record<string, { state: unknown; isLoading: boolean }>
|
||||
) {
|
||||
const datasourceLayers: Record<string, DatasourcePublicAPI> = {};
|
||||
|
@ -78,7 +80,7 @@ export const createDatasourceLayers = memoizeOne(function createDatasourceLayers
|
|||
|
||||
export async function persistedStateToExpression(
|
||||
datasources: Record<string, Datasource>,
|
||||
visualizations: Record<string, Visualization>,
|
||||
visualizations: VisualizationMap,
|
||||
doc: Document
|
||||
): Promise<{ ast: Ast | null; errors: ErrorMessage[] | undefined }> {
|
||||
const {
|
||||
|
|
|
@ -18,6 +18,8 @@ import {
|
|||
TableSuggestion,
|
||||
DatasourceSuggestion,
|
||||
DatasourcePublicAPI,
|
||||
DatasourceMap,
|
||||
VisualizationMap,
|
||||
} from '../../types';
|
||||
import { DragDropIdentifier } from '../../drag_drop';
|
||||
import { LensDispatch, selectSuggestion, switchVisualization } from '../../state_management';
|
||||
|
@ -57,7 +59,7 @@ export function getSuggestions({
|
|||
activeData,
|
||||
mainPalette,
|
||||
}: {
|
||||
datasourceMap: Record<string, Datasource>;
|
||||
datasourceMap: DatasourceMap;
|
||||
datasourceStates: Record<
|
||||
string,
|
||||
{
|
||||
|
@ -65,7 +67,7 @@ export function getSuggestions({
|
|||
state: unknown;
|
||||
}
|
||||
>;
|
||||
visualizationMap: Record<string, Visualization>;
|
||||
visualizationMap: VisualizationMap;
|
||||
activeVisualizationId: string | null;
|
||||
subVisualizationId?: string;
|
||||
visualizationState: unknown;
|
||||
|
@ -140,7 +142,7 @@ export function getVisualizeFieldSuggestions({
|
|||
visualizationState,
|
||||
visualizeTriggerFieldContext,
|
||||
}: {
|
||||
datasourceMap: Record<string, Datasource>;
|
||||
datasourceMap: DatasourceMap;
|
||||
datasourceStates: Record<
|
||||
string,
|
||||
{
|
||||
|
@ -148,7 +150,7 @@ export function getVisualizeFieldSuggestions({
|
|||
state: unknown;
|
||||
}
|
||||
>;
|
||||
visualizationMap: Record<string, Visualization>;
|
||||
visualizationMap: VisualizationMap;
|
||||
activeVisualizationId: string | null;
|
||||
subVisualizationId?: string;
|
||||
visualizationState: unknown;
|
||||
|
|
|
@ -25,7 +25,14 @@ import { Ast, toExpression } from '@kbn/interpreter/common';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import classNames from 'classnames';
|
||||
import { ExecutionContextSearch } from 'src/plugins/data/public';
|
||||
import { Datasource, Visualization, FramePublicAPI, DatasourcePublicAPI } from '../../types';
|
||||
import {
|
||||
Datasource,
|
||||
Visualization,
|
||||
FramePublicAPI,
|
||||
DatasourcePublicAPI,
|
||||
DatasourceMap,
|
||||
VisualizationMap,
|
||||
} from '../../types';
|
||||
import { getSuggestions, switchToSuggestion } from './suggestion_helpers';
|
||||
import {
|
||||
ReactExpressionRendererProps,
|
||||
|
@ -45,7 +52,7 @@ const MAX_SUGGESTIONS_DISPLAYED = 5;
|
|||
|
||||
export interface SuggestionPanelProps {
|
||||
activeDatasourceId: string | null;
|
||||
datasourceMap: Record<string, Datasource>;
|
||||
datasourceMap: DatasourceMap;
|
||||
datasourceStates: Record<
|
||||
string,
|
||||
{
|
||||
|
@ -54,7 +61,7 @@ export interface SuggestionPanelProps {
|
|||
}
|
||||
>;
|
||||
activeVisualizationId: string | null;
|
||||
visualizationMap: Record<string, Visualization>;
|
||||
visualizationMap: VisualizationMap;
|
||||
visualizationState: unknown;
|
||||
ExpressionRenderer: ReactExpressionRendererType;
|
||||
frame: FramePublicAPI;
|
||||
|
|
|
@ -20,7 +20,13 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { Visualization, FramePublicAPI, Datasource, VisualizationType } from '../../../types';
|
||||
import {
|
||||
Visualization,
|
||||
FramePublicAPI,
|
||||
VisualizationType,
|
||||
VisualizationMap,
|
||||
DatasourceMap,
|
||||
} from '../../../types';
|
||||
import { getSuggestions, switchToSuggestion, Suggestion } from '../suggestion_helpers';
|
||||
import { trackUiEvent } from '../../../lens_ui_telemetry';
|
||||
import { ToolbarButton } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
|
@ -44,9 +50,9 @@ interface VisualizationSelection {
|
|||
}
|
||||
|
||||
interface Props {
|
||||
visualizationMap: Record<string, Visualization>;
|
||||
framePublicAPI: FramePublicAPI;
|
||||
datasourceMap: Record<string, Datasource>;
|
||||
visualizationMap: VisualizationMap;
|
||||
datasourceMap: DatasourceMap;
|
||||
}
|
||||
|
||||
type SelectableEntry = EuiSelectableOption<{ value: string }>;
|
||||
|
@ -55,7 +61,7 @@ function VisualizationSummary({
|
|||
visualizationMap,
|
||||
visualization,
|
||||
}: {
|
||||
visualizationMap: Record<string, Visualization>;
|
||||
visualizationMap: VisualizationMap;
|
||||
visualization: {
|
||||
activeId: string | null;
|
||||
state: unknown;
|
||||
|
|
|
@ -34,12 +34,12 @@ import {
|
|||
ReactExpressionRendererType,
|
||||
} from '../../../../../../../src/plugins/expressions/public';
|
||||
import {
|
||||
Datasource,
|
||||
Visualization,
|
||||
FramePublicAPI,
|
||||
isLensBrushEvent,
|
||||
isLensFilterEvent,
|
||||
isLensEditEvent,
|
||||
VisualizationMap,
|
||||
DatasourceMap,
|
||||
} from '../../../types';
|
||||
import { DragDrop, DragContext, DragDropIdentifier } from '../../../drag_drop';
|
||||
import { Suggestion, switchToSuggestion } from '../suggestion_helpers';
|
||||
|
@ -62,10 +62,10 @@ import {
|
|||
|
||||
export interface WorkspacePanelProps {
|
||||
activeVisualizationId: string | null;
|
||||
visualizationMap: Record<string, Visualization>;
|
||||
visualizationMap: VisualizationMap;
|
||||
visualizationState: unknown;
|
||||
activeDatasourceId: string | null;
|
||||
datasourceMap: Record<string, Datasource>;
|
||||
datasourceMap: DatasourceMap;
|
||||
datasourceStates: Record<
|
||||
string,
|
||||
{
|
||||
|
|
|
@ -10,7 +10,7 @@ import './workspace_panel_wrapper.scss';
|
|||
import React, { useCallback } from 'react';
|
||||
import { EuiPageContent, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import classNames from 'classnames';
|
||||
import { Datasource, FramePublicAPI, Visualization } from '../../../types';
|
||||
import { DatasourceMap, FramePublicAPI, VisualizationMap } from '../../../types';
|
||||
import { NativeRenderer } from '../../../native_renderer';
|
||||
import { ChartSwitch } from './chart_switch';
|
||||
import { WarningsPopover } from './warnings_popover';
|
||||
|
@ -21,9 +21,9 @@ export interface WorkspacePanelWrapperProps {
|
|||
children: React.ReactNode | React.ReactNode[];
|
||||
framePublicAPI: FramePublicAPI;
|
||||
visualizationState: unknown;
|
||||
visualizationMap: Record<string, Visualization>;
|
||||
visualizationMap: VisualizationMap;
|
||||
visualizationId: string | null;
|
||||
datasourceMap: Record<string, Datasource>;
|
||||
datasourceMap: DatasourceMap;
|
||||
datasourceStates: Record<
|
||||
string,
|
||||
{
|
||||
|
|
|
@ -16,6 +16,7 @@ import moment from 'moment';
|
|||
import { Provider } from 'react-redux';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { ReactExpressionRendererProps } from 'src/plugins/expressions/public';
|
||||
import { DeepPartial } from '@reduxjs/toolkit';
|
||||
import { LensPublicStart } from '.';
|
||||
import { visualizationTypes } from './xy_visualization/types';
|
||||
import { navigationPluginMock } from '../../../../src/plugins/navigation/public/mocks';
|
||||
|
@ -35,7 +36,7 @@ import {
|
|||
import { LensAttributeService } from './lens_attribute_service';
|
||||
import { EmbeddableStateTransfer } from '../../../../src/plugins/embeddable/public';
|
||||
|
||||
import { makeConfigureStore, getPreloadedState, LensAppState } from './state_management/index';
|
||||
import { makeConfigureStore, LensAppState, LensState } from './state_management/index';
|
||||
import { getResolvedDateRange } from './utils';
|
||||
import { presentationUtilPluginMock } from '../../../../src/plugins/presentation_util/public/mocks';
|
||||
import { DatasourcePublicAPI, Datasource, Visualization, FramePublicAPI } from './types';
|
||||
|
@ -82,6 +83,11 @@ export function createMockVisualization(): jest.Mocked<Visualization> {
|
|||
};
|
||||
}
|
||||
|
||||
const visualizationMap = {
|
||||
vis: createMockVisualization(),
|
||||
vis2: createMockVisualization(),
|
||||
};
|
||||
|
||||
export type DatasourceMock = jest.Mocked<Datasource> & {
|
||||
publicAPIMock: jest.Mocked<DatasourcePublicAPI>;
|
||||
};
|
||||
|
@ -126,6 +132,13 @@ export function createMockDatasource(id: string): DatasourceMock {
|
|||
};
|
||||
}
|
||||
|
||||
const mockDatasource: DatasourceMock = createMockDatasource('testDatasource');
|
||||
const mockDatasource2: DatasourceMock = createMockDatasource('testDatasource2');
|
||||
const datasourceMap = {
|
||||
testDatasource2: mockDatasource2,
|
||||
testDatasource: mockDatasource,
|
||||
};
|
||||
|
||||
export function createExpressionRendererMock(): jest.Mock<
|
||||
React.ReactElement,
|
||||
[ReactExpressionRendererProps]
|
||||
|
@ -401,17 +414,21 @@ export function makeLensStore({
|
|||
data = mockDataPlugin();
|
||||
}
|
||||
const lensStore = makeConfigureStore(
|
||||
getPreloadedState({
|
||||
...defaultState,
|
||||
searchSessionId: data.search.session.start(),
|
||||
query: data.query.queryString.getQuery(),
|
||||
filters: data.query.filterManager.getGlobalFilters(),
|
||||
resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter),
|
||||
...preloadedState,
|
||||
}),
|
||||
{
|
||||
data,
|
||||
}
|
||||
lensServices: { ...makeDefaultServices(), data },
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
{
|
||||
lens: {
|
||||
...defaultState,
|
||||
searchSessionId: data.search.session.start(),
|
||||
query: data.query.queryString.getQuery(),
|
||||
filters: data.query.filterManager.getGlobalFilters(),
|
||||
resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter),
|
||||
...preloadedState,
|
||||
},
|
||||
} as DeepPartial<LensState>
|
||||
);
|
||||
|
||||
const origDispatch = lensStore.dispatch;
|
||||
|
|
|
@ -5,16 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { configureStore, DeepPartial, getDefaultMiddleware } from '@reduxjs/toolkit';
|
||||
import { configureStore, getDefaultMiddleware, DeepPartial } from '@reduxjs/toolkit';
|
||||
import logger from 'redux-logger';
|
||||
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
|
||||
import { lensSlice, initialState } from './lens_slice';
|
||||
import { lensSlice } from './lens_slice';
|
||||
import { timeRangeMiddleware } from './time_range_middleware';
|
||||
import { optimizingMiddleware } from './optimizing_middleware';
|
||||
import { externalContextMiddleware } from './external_context_middleware';
|
||||
|
||||
import { DataPublicPluginStart } from '../../../../../src/plugins/data/public';
|
||||
import { LensAppState, LensState } from './types';
|
||||
import { LensState, LensStoreDeps } from './types';
|
||||
import { initMiddleware } from './init_middleware';
|
||||
export * from './types';
|
||||
|
||||
export const reducer = {
|
||||
|
@ -22,8 +20,9 @@ export const reducer = {
|
|||
};
|
||||
|
||||
export const {
|
||||
setState,
|
||||
loadInitial,
|
||||
navigateAway,
|
||||
setState,
|
||||
setSaveable,
|
||||
onActiveDataChange,
|
||||
updateState,
|
||||
|
@ -38,29 +37,17 @@ export const {
|
|||
setToggleFullscreen,
|
||||
} = lensSlice.actions;
|
||||
|
||||
export const getPreloadedState = (initializedState: Partial<LensAppState>) => {
|
||||
const state = {
|
||||
lens: {
|
||||
...initialState,
|
||||
...initializedState,
|
||||
},
|
||||
} as DeepPartial<LensState>;
|
||||
return state;
|
||||
};
|
||||
|
||||
type PreloadedState = ReturnType<typeof getPreloadedState>;
|
||||
|
||||
export const makeConfigureStore = (
|
||||
preloadedState: PreloadedState,
|
||||
{ data }: { data: DataPublicPluginStart }
|
||||
storeDeps: LensStoreDeps,
|
||||
preloadedState: DeepPartial<LensState>
|
||||
) => {
|
||||
const middleware = [
|
||||
...getDefaultMiddleware({
|
||||
serializableCheck: false,
|
||||
}),
|
||||
initMiddleware(storeDeps),
|
||||
optimizingMiddleware(),
|
||||
timeRangeMiddleware(data),
|
||||
externalContextMiddleware(data),
|
||||
timeRangeMiddleware(storeDeps.lensServices.data),
|
||||
];
|
||||
if (process.env.NODE_ENV === 'development') middleware.push(logger);
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 { Dispatch, MiddlewareAPI, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { LensStoreDeps } from '..';
|
||||
import { lensSlice } from '../lens_slice';
|
||||
import { loadInitial } from './load_initial';
|
||||
import { subscribeToExternalContext } from './subscribe_to_external_context';
|
||||
|
||||
export const initMiddleware = (storeDeps: LensStoreDeps) => (store: MiddlewareAPI) => {
|
||||
const unsubscribeFromExternalContext = subscribeToExternalContext(
|
||||
storeDeps.lensServices.data,
|
||||
store.getState,
|
||||
store.dispatch
|
||||
);
|
||||
return (next: Dispatch) => (action: PayloadAction) => {
|
||||
if (lensSlice.actions.loadInitial.match(action)) {
|
||||
return loadInitial(
|
||||
store,
|
||||
storeDeps,
|
||||
action.payload.redirectCallback,
|
||||
action.payload.initialInput
|
||||
);
|
||||
} else if (lensSlice.actions.navigateAway.match(action)) {
|
||||
return unsubscribeFromExternalContext();
|
||||
}
|
||||
next(action);
|
||||
};
|
||||
};
|
|
@ -4,11 +4,17 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { makeDefaultServices, makeLensStore, defaultDoc, createMockVisualization } from '../mocks';
|
||||
import { createMockDatasource, DatasourceMock } from '../mocks';
|
||||
import {
|
||||
makeDefaultServices,
|
||||
makeLensStore,
|
||||
defaultDoc,
|
||||
createMockVisualization,
|
||||
createMockDatasource,
|
||||
DatasourceMock,
|
||||
} from '../../mocks';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { loadInitialStore } from './mounter';
|
||||
import { LensEmbeddableInput } from '../embeddable/embeddable';
|
||||
import { loadInitial } from './load_initial';
|
||||
import { LensEmbeddableInput } from '../../embeddable';
|
||||
|
||||
const defaultSavedObjectId = '1234';
|
||||
const preloadedState = {
|
||||
|
@ -20,7 +26,6 @@ const preloadedState = {
|
|||
};
|
||||
|
||||
describe('Mounter', () => {
|
||||
const byValueFlag = { allowByValueEmbeddables: true };
|
||||
const mockDatasource: DatasourceMock = createMockDatasource('testDatasource');
|
||||
const mockDatasource2: DatasourceMock = createMockDatasource('testDatasource2');
|
||||
const datasourceMap = {
|
||||
|
@ -66,15 +71,15 @@ describe('Mounter', () => {
|
|||
preloadedState,
|
||||
});
|
||||
await act(async () => {
|
||||
await loadInitialStore(
|
||||
redirectCallback,
|
||||
undefined,
|
||||
services,
|
||||
await loadInitial(
|
||||
lensStore,
|
||||
undefined,
|
||||
byValueFlag,
|
||||
datasourceMap,
|
||||
visualizationMap
|
||||
{
|
||||
lensServices: services,
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
redirectCallback,
|
||||
({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput
|
||||
);
|
||||
});
|
||||
expect(mockDatasource.initialize).toHaveBeenCalled();
|
||||
|
@ -87,15 +92,14 @@ describe('Mounter', () => {
|
|||
|
||||
const lensStore = await makeLensStore({ data: services.data, preloadedState });
|
||||
await act(async () => {
|
||||
await loadInitialStore(
|
||||
redirectCallback,
|
||||
undefined,
|
||||
services,
|
||||
await loadInitial(
|
||||
lensStore,
|
||||
undefined,
|
||||
byValueFlag,
|
||||
datasourceMap,
|
||||
visualizationMap
|
||||
{
|
||||
lensServices: services,
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
redirectCallback
|
||||
);
|
||||
});
|
||||
expect(mockDatasource.initialize).toHaveBeenCalled();
|
||||
|
@ -114,20 +118,19 @@ describe('Mounter', () => {
|
|||
// it.skip('should pass the datasource api for each layer to the visualization', async () => {})
|
||||
// it('displays errors from the frame in a toast', async () => {
|
||||
|
||||
describe('loadInitialStore', () => {
|
||||
describe('loadInitial', () => {
|
||||
it('does not load a document if there is no initial input', async () => {
|
||||
const services = makeDefaultServices();
|
||||
const redirectCallback = jest.fn();
|
||||
const lensStore = makeLensStore({ data: services.data, preloadedState });
|
||||
await loadInitialStore(
|
||||
redirectCallback,
|
||||
undefined,
|
||||
services,
|
||||
await loadInitial(
|
||||
lensStore,
|
||||
undefined,
|
||||
byValueFlag,
|
||||
datasourceMap,
|
||||
visualizationMap
|
||||
{
|
||||
lensServices: services,
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
redirectCallback
|
||||
);
|
||||
expect(services.attributeService.unwrapAttributes).not.toHaveBeenCalled();
|
||||
});
|
||||
|
@ -139,15 +142,15 @@ describe('Mounter', () => {
|
|||
|
||||
const lensStore = await makeLensStore({ data: services.data, preloadedState });
|
||||
await act(async () => {
|
||||
await loadInitialStore(
|
||||
redirectCallback,
|
||||
{ savedObjectId: defaultSavedObjectId } as LensEmbeddableInput,
|
||||
services,
|
||||
await loadInitial(
|
||||
lensStore,
|
||||
undefined,
|
||||
byValueFlag,
|
||||
datasourceMap,
|
||||
visualizationMap
|
||||
{
|
||||
lensServices: services,
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
redirectCallback,
|
||||
({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -175,43 +178,43 @@ describe('Mounter', () => {
|
|||
const lensStore = makeLensStore({ data: services.data, preloadedState });
|
||||
|
||||
await act(async () => {
|
||||
await loadInitialStore(
|
||||
redirectCallback,
|
||||
{ savedObjectId: defaultSavedObjectId } as LensEmbeddableInput,
|
||||
services,
|
||||
await loadInitial(
|
||||
lensStore,
|
||||
undefined,
|
||||
byValueFlag,
|
||||
datasourceMap,
|
||||
visualizationMap
|
||||
{
|
||||
lensServices: services,
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
redirectCallback,
|
||||
({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput
|
||||
);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await loadInitialStore(
|
||||
redirectCallback,
|
||||
{ savedObjectId: defaultSavedObjectId } as LensEmbeddableInput,
|
||||
services,
|
||||
await loadInitial(
|
||||
lensStore,
|
||||
undefined,
|
||||
byValueFlag,
|
||||
datasourceMap,
|
||||
visualizationMap
|
||||
{
|
||||
lensServices: services,
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
redirectCallback,
|
||||
({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput
|
||||
);
|
||||
});
|
||||
|
||||
expect(services.attributeService.unwrapAttributes).toHaveBeenCalledTimes(1);
|
||||
|
||||
await act(async () => {
|
||||
await loadInitialStore(
|
||||
redirectCallback,
|
||||
{ savedObjectId: '5678' } as LensEmbeddableInput,
|
||||
services,
|
||||
await loadInitial(
|
||||
lensStore,
|
||||
undefined,
|
||||
byValueFlag,
|
||||
datasourceMap,
|
||||
visualizationMap
|
||||
{
|
||||
lensServices: services,
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
redirectCallback,
|
||||
({ savedObjectId: '5678' } as unknown) as LensEmbeddableInput
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -227,15 +230,15 @@ describe('Mounter', () => {
|
|||
services.attributeService.unwrapAttributes = jest.fn().mockRejectedValue('failed to load');
|
||||
|
||||
await act(async () => {
|
||||
await loadInitialStore(
|
||||
redirectCallback,
|
||||
{ savedObjectId: defaultSavedObjectId } as LensEmbeddableInput,
|
||||
services,
|
||||
await loadInitial(
|
||||
lensStore,
|
||||
undefined,
|
||||
byValueFlag,
|
||||
datasourceMap,
|
||||
visualizationMap
|
||||
{
|
||||
lensServices: services,
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
redirectCallback,
|
||||
({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput
|
||||
);
|
||||
});
|
||||
expect(services.attributeService.unwrapAttributes).toHaveBeenCalledWith({
|
||||
|
@ -251,15 +254,15 @@ describe('Mounter', () => {
|
|||
const services = makeDefaultServices();
|
||||
const lensStore = makeLensStore({ data: services.data, preloadedState });
|
||||
await act(async () => {
|
||||
await loadInitialStore(
|
||||
redirectCallback,
|
||||
({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput,
|
||||
services,
|
||||
await loadInitial(
|
||||
lensStore,
|
||||
undefined,
|
||||
byValueFlag,
|
||||
datasourceMap,
|
||||
visualizationMap
|
||||
{
|
||||
lensServices: services,
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
redirectCallback,
|
||||
({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput
|
||||
);
|
||||
});
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* 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 { MiddlewareAPI } from '@reduxjs/toolkit';
|
||||
import { isEqual } from 'lodash';
|
||||
import { setState } from '..';
|
||||
import { updateLayer, updateVisualizationState, LensStoreDeps } from '..';
|
||||
import { LensEmbeddableInput, LensByReferenceInput } from '../../embeddable/embeddable';
|
||||
import { getInitialDatasourceId } from '../../utils';
|
||||
import { initializeDatasources } from '../../editor_frame_service/editor_frame';
|
||||
import { generateId } from '../../id_generator';
|
||||
import {
|
||||
getVisualizeFieldSuggestions,
|
||||
switchToSuggestion,
|
||||
} from '../../editor_frame_service/editor_frame/suggestion_helpers';
|
||||
import { getPersistedDoc } from '../../app_plugin/save_modal_container';
|
||||
|
||||
export function loadInitial(
|
||||
store: MiddlewareAPI,
|
||||
{
|
||||
lensServices,
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
embeddableEditorIncomingState,
|
||||
initialContext,
|
||||
}: LensStoreDeps,
|
||||
redirectCallback: (savedObjectId?: string) => void,
|
||||
initialInput?: LensEmbeddableInput
|
||||
) {
|
||||
const { getState, dispatch } = store;
|
||||
const { attributeService, chrome, notifications, data, dashboardFeatureFlag } = lensServices;
|
||||
const { persistedDoc } = getState().lens;
|
||||
if (
|
||||
!initialInput ||
|
||||
(attributeService.inputIsRefType(initialInput) &&
|
||||
initialInput.savedObjectId === persistedDoc?.savedObjectId)
|
||||
) {
|
||||
return initializeDatasources(
|
||||
datasourceMap,
|
||||
getState().lens.datasourceStates,
|
||||
undefined,
|
||||
initialContext,
|
||||
{
|
||||
isFullEditor: true,
|
||||
}
|
||||
)
|
||||
.then((result) => {
|
||||
const datasourceStates = Object.entries(result).reduce(
|
||||
(state, [datasourceId, datasourceState]) => ({
|
||||
...state,
|
||||
[datasourceId]: {
|
||||
...datasourceState,
|
||||
isLoading: false,
|
||||
},
|
||||
}),
|
||||
{}
|
||||
);
|
||||
dispatch(
|
||||
setState({
|
||||
datasourceStates,
|
||||
isLoading: false,
|
||||
})
|
||||
);
|
||||
if (initialContext) {
|
||||
const selectedSuggestion = getVisualizeFieldSuggestions({
|
||||
datasourceMap,
|
||||
datasourceStates,
|
||||
visualizationMap,
|
||||
activeVisualizationId: Object.keys(visualizationMap)[0] || null,
|
||||
visualizationState: null,
|
||||
visualizeTriggerFieldContext: initialContext,
|
||||
});
|
||||
if (selectedSuggestion) {
|
||||
switchToSuggestion(dispatch, selectedSuggestion, 'SWITCH_VISUALIZATION');
|
||||
}
|
||||
}
|
||||
const activeDatasourceId = getInitialDatasourceId(datasourceMap);
|
||||
const visualization = getState().lens.visualization;
|
||||
const activeVisualization =
|
||||
visualization.activeId && visualizationMap[visualization.activeId];
|
||||
|
||||
if (visualization.state === null && activeVisualization) {
|
||||
const newLayerId = generateId();
|
||||
|
||||
const initialVisualizationState = activeVisualization.initialize(() => newLayerId);
|
||||
dispatch(
|
||||
updateLayer({
|
||||
datasourceId: activeDatasourceId!,
|
||||
layerId: newLayerId,
|
||||
updater: datasourceMap[activeDatasourceId!].insertLayer,
|
||||
})
|
||||
);
|
||||
dispatch(
|
||||
updateVisualizationState({
|
||||
visualizationId: activeVisualization.id,
|
||||
updater: initialVisualizationState,
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((e: { message: string }) => {
|
||||
notifications.toasts.addDanger({
|
||||
title: e.message,
|
||||
});
|
||||
redirectCallback();
|
||||
});
|
||||
}
|
||||
getPersistedDoc({
|
||||
initialInput,
|
||||
attributeService,
|
||||
data,
|
||||
chrome,
|
||||
notifications,
|
||||
})
|
||||
.then(
|
||||
(doc) => {
|
||||
if (doc) {
|
||||
const currentSessionId = data.search.session.getSessionId();
|
||||
const docDatasourceStates = Object.entries(doc.state.datasourceStates).reduce(
|
||||
(stateMap, [datasourceId, datasourceState]) => ({
|
||||
...stateMap,
|
||||
[datasourceId]: {
|
||||
isLoading: true,
|
||||
state: datasourceState,
|
||||
},
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
initializeDatasources(
|
||||
datasourceMap,
|
||||
docDatasourceStates,
|
||||
doc.references,
|
||||
initialContext,
|
||||
{
|
||||
isFullEditor: true,
|
||||
}
|
||||
)
|
||||
.then((result) => {
|
||||
const activeDatasourceId = getInitialDatasourceId(datasourceMap, doc);
|
||||
|
||||
dispatch(
|
||||
setState({
|
||||
query: doc.state.query,
|
||||
searchSessionId:
|
||||
dashboardFeatureFlag.allowByValueEmbeddables &&
|
||||
Boolean(embeddableEditorIncomingState?.originatingApp) &&
|
||||
!(initialInput as LensByReferenceInput)?.savedObjectId &&
|
||||
currentSessionId
|
||||
? currentSessionId
|
||||
: data.search.session.start(),
|
||||
...(!isEqual(persistedDoc, doc) ? { persistedDoc: doc } : null),
|
||||
activeDatasourceId,
|
||||
visualization: {
|
||||
activeId: doc.visualizationType,
|
||||
state: doc.state.visualization,
|
||||
},
|
||||
datasourceStates: Object.entries(result).reduce(
|
||||
(state, [datasourceId, datasourceState]) => ({
|
||||
...state,
|
||||
[datasourceId]: {
|
||||
...datasourceState,
|
||||
isLoading: false,
|
||||
},
|
||||
}),
|
||||
{}
|
||||
),
|
||||
isLoading: false,
|
||||
})
|
||||
);
|
||||
})
|
||||
.catch((e: { message: string }) =>
|
||||
notifications.toasts.addDanger({
|
||||
title: e.message,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
redirectCallback();
|
||||
}
|
||||
},
|
||||
() => {
|
||||
dispatch(
|
||||
setState({
|
||||
isLoading: false,
|
||||
})
|
||||
);
|
||||
redirectCallback();
|
||||
}
|
||||
)
|
||||
.catch((e: { message: string }) =>
|
||||
notifications.toasts.addDanger({
|
||||
title: e.message,
|
||||
})
|
||||
);
|
||||
}
|
|
@ -7,34 +7,15 @@
|
|||
|
||||
import { delay, finalize, switchMap, tap } from 'rxjs/operators';
|
||||
import { debounce, isEqual } from 'lodash';
|
||||
import { Dispatch, MiddlewareAPI, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { trackUiEvent } from '../lens_ui_telemetry';
|
||||
|
||||
import { trackUiEvent } from '../../lens_ui_telemetry';
|
||||
import {
|
||||
waitUntilNextSessionCompletes$,
|
||||
DataPublicPluginStart,
|
||||
} from '../../../../../src/plugins/data/public';
|
||||
import { setState, LensGetState, LensDispatch } from '.';
|
||||
import { LensAppState } from './types';
|
||||
import { getResolvedDateRange } from '../utils';
|
||||
} from '../../../../../../src/plugins/data/public';
|
||||
import { setState, LensGetState, LensDispatch } from '..';
|
||||
import { getResolvedDateRange } from '../../utils';
|
||||
|
||||
export const externalContextMiddleware = (data: DataPublicPluginStart) => (
|
||||
store: MiddlewareAPI
|
||||
) => {
|
||||
const unsubscribeFromExternalContext = subscribeToExternalContext(
|
||||
data,
|
||||
store.getState,
|
||||
store.dispatch
|
||||
);
|
||||
return (next: Dispatch) => (action: PayloadAction<Partial<LensAppState>>) => {
|
||||
if (action.type === 'lens/navigateAway') {
|
||||
unsubscribeFromExternalContext();
|
||||
}
|
||||
next(action);
|
||||
};
|
||||
};
|
||||
|
||||
function subscribeToExternalContext(
|
||||
export function subscribeToExternalContext(
|
||||
data: DataPublicPluginStart,
|
||||
getState: LensGetState,
|
||||
dispatch: LensDispatch
|
|
@ -6,8 +6,10 @@
|
|||
*/
|
||||
|
||||
import { createSlice, current, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { LensEmbeddableInput } from '..';
|
||||
import { TableInspectorAdapter } from '../editor_frame_service/types';
|
||||
import { LensAppState } from './types';
|
||||
import { getInitialDatasourceId, getResolvedDateRange } from '../utils';
|
||||
import { LensAppState, LensStoreDeps } from './types';
|
||||
|
||||
export const initialState: LensAppState = {
|
||||
searchSessionId: '',
|
||||
|
@ -26,6 +28,44 @@ export const initialState: LensAppState = {
|
|||
},
|
||||
};
|
||||
|
||||
export const getPreloadedState = ({
|
||||
lensServices: { data },
|
||||
initialContext,
|
||||
embeddableEditorIncomingState,
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
}: LensStoreDeps) => {
|
||||
const initialDatasourceId = getInitialDatasourceId(datasourceMap);
|
||||
const datasourceStates: LensAppState['datasourceStates'] = {};
|
||||
if (initialDatasourceId) {
|
||||
datasourceStates[initialDatasourceId] = {
|
||||
state: null,
|
||||
isLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
const state = {
|
||||
...initialState,
|
||||
isLoading: true,
|
||||
query: data.query.queryString.getQuery(),
|
||||
// Do not use app-specific filters from previous app,
|
||||
// only if Lens was opened with the intention to visualize a field (e.g. coming from Discover)
|
||||
filters: !initialContext
|
||||
? data.query.filterManager.getGlobalFilters()
|
||||
: data.query.filterManager.getFilters(),
|
||||
searchSessionId: data.search.session.getSessionId(),
|
||||
resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter),
|
||||
isLinkedToOriginatingApp: Boolean(embeddableEditorIncomingState?.originatingApp),
|
||||
activeDatasourceId: initialDatasourceId,
|
||||
datasourceStates,
|
||||
visualization: {
|
||||
state: null as unknown,
|
||||
activeId: Object.keys(visualizationMap)[0] || null,
|
||||
},
|
||||
};
|
||||
return state;
|
||||
};
|
||||
|
||||
export const lensSlice = createSlice({
|
||||
name: 'lens',
|
||||
initialState,
|
||||
|
@ -254,6 +294,13 @@ export const lensSlice = createSlice({
|
|||
};
|
||||
},
|
||||
navigateAway: (state) => state,
|
||||
loadInitial: (
|
||||
state,
|
||||
payload: PayloadAction<{
|
||||
initialInput?: LensEmbeddableInput;
|
||||
redirectCallback: (savedObjectId?: string) => void;
|
||||
}>
|
||||
) => state,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Dispatch, MiddlewareAPI, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { Dispatch, MiddlewareAPI, Action } from '@reduxjs/toolkit';
|
||||
import { isEqual } from 'lodash';
|
||||
import { LensAppState } from './types';
|
||||
import { lensSlice } from './lens_slice';
|
||||
|
||||
/** cancels updates to the store that don't change the state */
|
||||
export const optimizingMiddleware = () => (store: MiddlewareAPI) => {
|
||||
return (next: Dispatch) => (action: PayloadAction<Partial<LensAppState>>) => {
|
||||
if (action.type === 'lens/onActiveDataChange') {
|
||||
return (next: Dispatch) => (action: Action) => {
|
||||
if (lensSlice.actions.onActiveDataChange.match(action)) {
|
||||
if (isEqual(store.getState().lens.activeData, action.payload)) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -5,11 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { VisualizeFieldContext } from 'src/plugins/ui_actions/public';
|
||||
import { EmbeddableEditorState } from 'src/plugins/embeddable/public';
|
||||
import { Filter, Query, SavedQuery } from '../../../../../src/plugins/data/public';
|
||||
import { Document } from '../persistence';
|
||||
|
||||
import { TableInspectorAdapter } from '../editor_frame_service/types';
|
||||
import { DateRange } from '../../common';
|
||||
import { LensAppServices } from '../app_plugin/types';
|
||||
import { DatasourceMap, VisualizationMap } from '../types';
|
||||
|
||||
export interface PreviewState {
|
||||
visualization: {
|
||||
|
@ -49,3 +53,11 @@ export type DispatchSetState = (
|
|||
export interface LensState {
|
||||
lens: LensAppState;
|
||||
}
|
||||
|
||||
export interface LensStoreDeps {
|
||||
lensServices: LensAppServices;
|
||||
datasourceMap: DatasourceMap;
|
||||
visualizationMap: VisualizationMap;
|
||||
initialContext?: VisualizeFieldContext;
|
||||
embeddableEditorIncomingState?: EmbeddableEditorState;
|
||||
}
|
||||
|
|
|
@ -45,10 +45,13 @@ export interface EditorFrameProps {
|
|||
showNoDataPopover: () => void;
|
||||
}
|
||||
|
||||
export type VisualizationMap = Record<string, Visualization>;
|
||||
export type DatasourceMap = Record<string, Datasource>;
|
||||
|
||||
export interface EditorFrameInstance {
|
||||
EditorFrameContainer: (props: EditorFrameProps) => React.ReactElement;
|
||||
datasourceMap: Record<string, Datasource>;
|
||||
visualizationMap: Record<string, Visualization>;
|
||||
datasourceMap: DatasourceMap;
|
||||
visualizationMap: VisualizationMap;
|
||||
}
|
||||
|
||||
export interface EditorFrameSetup {
|
||||
|
|
|
@ -13,7 +13,7 @@ import { SavedObjectReference } from 'kibana/public';
|
|||
import { Filter, Query } from 'src/plugins/data/public';
|
||||
import { uniq } from 'lodash';
|
||||
import { Document } from './persistence/saved_object_store';
|
||||
import { Datasource } from './types';
|
||||
import { Datasource, DatasourceMap } from './types';
|
||||
import { extractFilterReferences } from './persistence';
|
||||
|
||||
export function getVisualizeGeoFieldMessage(fieldType: string) {
|
||||
|
@ -55,10 +55,7 @@ export function getActiveDatasourceIdFromDoc(doc?: Document) {
|
|||
return firstDatasourceFromDoc || null;
|
||||
}
|
||||
|
||||
export const getInitialDatasourceId = (
|
||||
datasourceMap: Record<string, Datasource>,
|
||||
doc?: Document
|
||||
) => {
|
||||
export const getInitialDatasourceId = (datasourceMap: DatasourceMap, doc?: Document) => {
|
||||
return (doc && getActiveDatasourceIdFromDoc(doc)) || Object.keys(datasourceMap)[0] || null;
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue