Skip to content

Commit

Permalink
[1][VisBuilder Migration] Add initial setup and migrate state management
Browse files Browse the repository at this point in the history
This PR completes Task 1 and 2 in opensearch-project#5407.

* Reconstruct and allow VisBuilder to be rendered from DataExplorer
* Follow proposal task 2 option 1 to migrate state management to DataExplorer

Issue Resolve
opensearch-project#5492
opensearch-project#5493

[2][VisBuilder Migration] Add context and implement side panel

* add useVisBuilderContext
* modify preloadedState in Data Explorer
* implement side panel

Issue Resolve:
opensearch-project#5522

Signed-off-by: ananzh <ananzh@amazon.com>

[3][VisBuilder Migration] Combine components into VisBuilderCanvas

Signed-off-by: ananzh <ananzh@amazon.com>
  • Loading branch information
ananzh committed Dec 18, 2023
1 parent d8cbc17 commit d9e383a
Show file tree
Hide file tree
Showing 104 changed files with 965 additions and 1,190 deletions.
10 changes: 9 additions & 1 deletion src/core/public/application/scoped_history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,10 @@ export class ScopedHistory<HistoryLocationState = unknown>
private setupHistoryListener() {
const unlisten = this.parentHistory.listen((location, action) => {
// If the user navigates outside the scope of this basePath, tear it down.
if (!location.pathname.startsWith(this.basePath)) {
if (
!location.pathname.startsWith(this.basePath) &&
!this.isPathnameAcceptable(location.pathname)
) {
unlisten();
this.isActive = false;
return;
Expand Down Expand Up @@ -340,4 +343,9 @@ export class ScopedHistory<HistoryLocationState = unknown>
});
});
}

private isPathnameAcceptable(pathname: string): boolean {
const normalizedPathname = pathname.replace('/data-explorer', '');
return normalizedPathname.startsWith(this.basePath);
}
}
2 changes: 2 additions & 0 deletions src/plugins/data_explorer/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export function plugin() {
export { DataExplorerPluginSetup, DataExplorerPluginStart, DataExplorerServices } from './types';
export { ViewProps, ViewDefinition, DefaultViewState } from './services/view_service';
export {
AppDispatch,
MetadataState,
RootState,
Store,
useTypedSelector,
Expand Down
12 changes: 10 additions & 2 deletions src/plugins/data_explorer/public/services/view_service/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Slice } from '@reduxjs/toolkit';
import { LazyExoticComponent } from 'react';
import { AppMountParameters } from '../../../../../core/public';
import { RootState } from '../../utils/state_management';
import { Store } from '../../utils/state_management';

interface ViewListItem {
id: string;
Expand All @@ -20,12 +21,19 @@ export interface DefaultViewState<T = unknown> {

export type ViewProps = AppMountParameters;

type SideEffect<T = any> = (store: Store, state: T, previousState?: T, services?: T) => void;

export interface ViewDefinition<T = any> {
readonly id: string;
readonly title: string;
readonly ui?: {
defaults: DefaultViewState | (() => DefaultViewState) | (() => Promise<DefaultViewState>);
slice: Slice<T>;
defaults:
| DefaultViewState
| (() => DefaultViewState)
| (() => Promise<DefaultViewState>)
| (() => Promise<Array<Promise<DefaultViewState<any>>>>);
slices: Array<Slice<T>>;
sideEffects?: Array<SideEffect<T>>;
};
readonly Canvas: LazyExoticComponent<(props: ViewProps) => React.ReactElement>;
readonly Panel: LazyExoticComponent<(props: ViewProps) => React.ReactElement>;
Expand Down
37 changes: 28 additions & 9 deletions src/plugins/data_explorer/public/utils/state_management/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { PreloadedState } from '@reduxjs/toolkit';
import { getPreloadedState as getPreloadedMetadataState } from './metadata_slice';
import { RootState } from './store';
import { DataExplorerServices } from '../../types';
import { DefaultViewState } from '../../services/view_service';

export const getPreloadedState = async (
services: DataExplorerServices
Expand All @@ -22,19 +23,37 @@ export const getPreloadedState = async (
return;
}

const { defaults } = view.ui;
const { defaults, slices } = view.ui;

try {
// defaults can be a function or an object
const preloadedState = typeof defaults === 'function' ? await defaults() : defaults;
rootState[view.id] = preloadedState.state;

// if the view wants to override the root state, we do that here
if (preloadedState.root) {
rootState = {
...rootState,
...preloadedState.root,
};
if (Array.isArray(preloadedState)) {
await Promise.all(
preloadedState.map(async (statePromise, index) => {
try {
const state = await statePromise;
const slice = slices[index];
const id = slice.name;
rootState[id] = state.state;
} catch (e) {
// eslint-disable-next-line no-console
console.error(`Error initializing slice: ${e}`);
}
})
);
} else {
slices.forEach((slice) => {
const id = slice.name;
rootState[id] = preloadedState.state;
});
// if the view wants to override the root state, we do that here
if (preloadedState.root) {
rootState = {
...rootState,
...preloadedState.root,
};
}
}
} catch (e) {
// eslint-disable-next-line no-console
Expand Down
35 changes: 27 additions & 8 deletions src/plugins/data_explorer/public/utils/state_management/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,18 @@ export const configurePreloadedStore = (preloadedState: PreloadedState<RootState
export const getPreloadedStore = async (services: DataExplorerServices) => {
// For each view preload the data and register the slice
const views = services.viewRegistry.all();
const viewSideEffectsMap: Record<string, Function[]> = {};

views.forEach((view) => {
if (!view.ui) return;

const { slice } = view.ui;
registerSlice(slice);
const { slices, sideEffects } = view.ui;
registerSlices(slices);

// Save side effects if they exist
if (sideEffects) {
viewSideEffectsMap[view.id] = sideEffects;
}
});

const preloadedState = await loadReduxState(services);
Expand All @@ -72,7 +79,17 @@ export const getPreloadedStore = async (services: DataExplorerServices) => {

if (isEqual(state, previousState)) return;

// Add Side effects here to apply after changes to the store are made. None for now.
// Execute view-specific side effects.
Object.entries(viewSideEffectsMap).forEach(([viewId, effects]) => {
effects.forEach((effect) => {
try {
effect(store, state, previousState, services);
} catch (e) {
// eslint-disable-next-line no-console
console.error(`Error executing side effect for view ${viewId}:`, e);
}
});
});

previousState = state;
};
Expand Down Expand Up @@ -103,11 +120,13 @@ export const getPreloadedStore = async (services: DataExplorerServices) => {
return { store, unsubscribe: onUnsubscribe };
};

export const registerSlice = (slice: Slice) => {
if (dynamicReducers[slice.name]) {
throw new Error(`Slice ${slice.name} already registered`);
}
dynamicReducers[slice.name] = slice.reducer;
export const registerSlices = (slices: Slice[]) => {
slices.forEach((slice) => {
if (dynamicReducers[slice.name]) {
throw new Error(`Slice ${slice.name} already registered`);
}
dynamicReducers[slice.name] = slice.reducer;
});
};

// Infer the `RootState` and `AppDispatch` types from the store itself
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/discover/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ export class DiscoverPlugin
const services = getServices();
return await getPreloadedState(services);
},
slice: discoverSlice,
slices: [discoverSlice],
},
shouldShow: () => true,
// ViewComponent
Expand Down
5 changes: 3 additions & 2 deletions src/plugins/vis_builder/opensearch_dashboards.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"navigation",
"savedObjects",
"visualizations",
"uiActions"
"uiActions",
"dataExplorer"
],
"requiredBundles": [
"charts",
Expand All @@ -21,4 +22,4 @@
"visDefaultEditor",
"visTypeVislib"
]
}
}
84 changes: 0 additions & 84 deletions src/plugins/vis_builder/public/application/app.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,15 @@
import { EuiForm } from '@elastic/eui';
import React from 'react';
import { useVisualizationType } from '../../utils/use';
import { useTypedSelector } from '../../utils/state_management';
import './config_panel.scss';
import { useSelector } from '../../utils/state_management';
import { mapSchemaToAggPanel } from './schema_to_dropbox';
import { SecondaryPanel } from './secondary_panel';

import './index.scss';

export function ConfigPanel() {
const vizType = useVisualizationType();
const editingState = useTypedSelector(
(state) => state.visualization.activeVisualization?.draftAgg
);
const editingState = useSelector((state) => state.vbVisualization.activeVisualization?.draftAgg);
const schemas = vizType.ui.containerConfig.data.schemas;

if (!schemas) return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,33 @@ import { cloneDeep, get } from 'lodash';
import { useDebounce } from 'react-use';
import { i18n } from '@osd/i18n';
import { EuiCallOut } from '@elastic/eui';
import { useTypedDispatch, useTypedSelector } from '../../utils/state_management';
import { useDispatch, useSelector } from '../../utils/state_management';
import { DefaultEditorAggParams } from '../../../../../vis_default_editor/public';
import { Title } from './title';
import { useIndexPatterns, useVisualizationType } from '../../utils/use';
import { useVisualizationType } from '../../utils/use';
import {
OpenSearchDashboardsContextProvider,
useOpenSearchDashboards,
} from '../../../../../opensearch_dashboards_react/public';
import { VisBuilderServices } from '../../../types';
import { useVisBuilderContext } from '../../view_components/context';
import { VisBuilderViewServices } from '../../../types';
import { AggParam, IAggType, IFieldParamType } from '../../../../../data/public';
import { saveDraftAgg, editDraftAgg } from '../../utils/state_management/visualization_slice';
import { setError } from '../../utils/state_management/metadata_slice';
import { setError } from '../../utils/state_management/editor_slice';
import { Storage } from '../../../../../opensearch_dashboards_utils/public';

const PANEL_KEY = 'SECONDARY_PANEL';

export function SecondaryPanel() {
const { draftAgg, aggConfigParams } = useTypedSelector(
(state) => state.visualization.activeVisualization!
const { draftAgg, aggConfigParams } = useSelector(
(state) => state.vbVisualization.activeVisualization!
);
const isEditorValid = useTypedSelector((state) => !state.metadata.editor.errors[PANEL_KEY]);
const isEditorValid = useSelector((state) => !state.vbEditor.errors[PANEL_KEY]);
const [touched, setTouched] = useState(false);
const dispatch = useTypedDispatch();
const dispatch = useDispatch();
const vizType = useVisualizationType();
const indexPattern = useIndexPatterns().selected;
const { services } = useOpenSearchDashboards<VisBuilderServices>();
const { indexPattern } = useVisBuilderContext();
const { services } = useOpenSearchDashboards<VisBuilderViewServices>();
const {
data: {
search: { aggs: aggService },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { cloneDeep } from 'lodash';
import { BucketAggType, IndexPatternField, propFilter } from '../../../../../../data/common';
import { Schema } from '../../../../../../vis_default_editor/public';
import { COUNT_FIELD, FieldDragDataType } from '../../../utils/drag_drop/types';
import { useTypedDispatch } from '../../../utils/state_management';
import { useDispatch } from '../../../utils/state_management';
import { DropboxDisplay, DropboxProps } from '../dropbox';
import { useDrop } from '../../../utils/drag_drop';
import {
Expand All @@ -31,7 +31,7 @@ export const useDropbox = (props: UseDropboxProps): DropboxProps => {
const { id: dropboxId, label, schema } = props;
const [validAggTypes, setValidAggTypes] = useState<string[]>([]);
const { aggConfigs, indexPattern, aggs, timeRange } = useAggs();
const dispatch = useTypedDispatch();
const dispatch = useDispatch();
const {
services: {
data: {
Expand Down
Loading

0 comments on commit d9e383a

Please sign in to comment.