Skip to content

Commit

Permalink
[Portable Dashboards] Prep Redux Tools (elastic#136572)
Browse files Browse the repository at this point in the history
* Created new Redux system for embeddables in Presentation Util. Migrated all Controls to use it.
  • Loading branch information
ThomThomson authored Jul 29, 2022
1 parent 4a95b2c commit 01fc584
Show file tree
Hide file tree
Showing 46 changed files with 1,192 additions and 950 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import type { Filter, Query, BoolQuery, TimeRange } from '@kbn/es-query';
import { FieldSpec, DataView, DataViewField } from '@kbn/data-views-plugin/common';
import { FieldSpec, DataView } from '@kbn/data-views-plugin/common';

import { DataControlInput } from '../../types';

Expand All @@ -17,10 +17,9 @@ export interface OptionsListEmbeddableInput extends DataControlInput {
selectedOptions?: string[];
runPastTimeout?: boolean;
singleSelect?: boolean;
loading?: boolean;
}

export type OptionsListField = DataViewField & {
export type OptionsListField = FieldSpec & {
textFieldName?: string;
parentFieldName?: string;
childFieldName?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {

import { useReduxContainerContext } from '@kbn/presentation-util-plugin/public';
import { ErrorEmbeddable } from '@kbn/embeddable-plugin/public';
import { ControlGroupInput } from '../types';
import { ControlGroupReduxState } from '../types';
import { pluginServices } from '../../services';
import { EditControlButton } from '../editor/edit_control';
import { ControlGroupStrings } from '../control_group_strings';
Expand All @@ -42,11 +42,11 @@ export const ControlFrame = ({
const [hasFatalError, setHasFatalError] = useState(false);

const {
useEmbeddableSelector,
useEmbeddableSelector: select,
containerActions: { untilEmbeddableLoaded, removeEmbeddable },
} = useReduxContainerContext<ControlGroupInput>();
} = useReduxContainerContext<ControlGroupReduxState>();

const { controlStyle } = useEmbeddableSelector((state) => state);
const controlStyle = select((state) => state.explicitInput.controlStyle);

// Controls Services Context
const { overlays } = pluginServices.getHooks();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,27 +28,31 @@ import {
useSensors,
LayoutMeasuringStrategy,
} from '@dnd-kit/core';

import { ViewMode } from '@kbn/embeddable-plugin/public';
import { useReduxContainerContext } from '@kbn/presentation-util-plugin/public';
import { ControlGroupInput } from '../types';

import { ControlGroupReduxState } from '../types';
import { controlGroupReducers } from '../state/control_group_reducers';
import { ControlClone, SortableControl } from './control_group_sortable_item';

export const ControlGroup = () => {
// Redux embeddable container Context
const reduxContainerContext = useReduxContainerContext<
ControlGroupInput,
ControlGroupReduxState,
typeof controlGroupReducers
>();
const {
useEmbeddableSelector,
useEmbeddableDispatch,
actions: { setControlOrders },
useEmbeddableSelector: select,
useEmbeddableDispatch,
} = reduxContainerContext;
const dispatch = useEmbeddableDispatch();

// current state
const { panels, viewMode, controlStyle } = useEmbeddableSelector((state) => state);
const panels = select((state) => state.explicitInput.panels);
const viewMode = select((state) => state.explicitInput.viewMode);
const controlStyle = select((state) => state.explicitInput.controlStyle);

const isEditable = viewMode === ViewMode.EDIT;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import { CSS } from '@dnd-kit/utilities';
import classNames from 'classnames';

import { useReduxContainerContext } from '@kbn/presentation-util-plugin/public';
import { ControlGroupInput } from '../types';
import { ControlFrame, ControlFrameProps } from './control_frame_component';
import { ControlGroupReduxState } from '../types';
import { ControlGroupStrings } from '../control_group_strings';

interface DragInfo {
Expand Down Expand Up @@ -67,8 +67,8 @@ const SortableControlInner = forwardRef<
dragHandleRef
) => {
const { isOver, isDragging, draggingIndex, index } = dragInfo;
const { useEmbeddableSelector } = useReduxContainerContext<ControlGroupInput>();
const { panels } = useEmbeddableSelector((state) => state);
const { useEmbeddableSelector } = useReduxContainerContext<ControlGroupReduxState>();
const panels = useEmbeddableSelector((state) => state.explicitInput.panels);

const grow = panels[embeddableId].grow;
const width = panels[embeddableId].width;
Expand Down Expand Up @@ -119,8 +119,9 @@ const SortableControlInner = forwardRef<
* can be quite cumbersome.
*/
export const ControlClone = ({ draggingId }: { draggingId: string }) => {
const { useEmbeddableSelector } = useReduxContainerContext<ControlGroupInput>();
const { panels, controlStyle } = useEmbeddableSelector((state) => state);
const { useEmbeddableSelector: select } = useReduxContainerContext<ControlGroupReduxState>();
const panels = select((state) => state.explicitInput.panels);
const controlStyle = select((state) => state.explicitInput.controlStyle);

const width = panels[draggingId].width;
const title = panels[draggingId].explicitInput.title;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { OverlayRef } from '@kbn/core/public';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { EmbeddableFactoryNotFoundError } from '@kbn/embeddable-plugin/public';
import { useReduxContainerContext } from '@kbn/presentation-util-plugin/public';
import { ControlGroupInput } from '../types';
import { ControlGroupReduxState } from '../types';
import { ControlEditor } from './control_editor';
import { pluginServices } from '../../services';
import { ControlGroupStrings } from '../control_group_strings';
Expand All @@ -41,7 +41,7 @@ export const EditControlButton = ({ embeddableId }: { embeddableId: string }) =>

// Redux embeddable container Context
const reduxContainerContext = useReduxContainerContext<
ControlGroupInput,
ControlGroupReduxState,
typeof controlGroupReducers
>();
const {
Expand All @@ -53,7 +53,7 @@ export const EditControlButton = ({ embeddableId }: { embeddableId: string }) =>
const dispatch = useEmbeddableDispatch();

// current state
const { panels } = useEmbeddableSelector((state) => state);
const panels = useEmbeddableSelector((state) => state.explicitInput.panels);

// keep up to date ref of latest panel state for comparison when closing editor.
const latestPanelState = useRef(panels[embeddableId]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,34 @@
* Side Public License, v 1.
*/

import {
map,
skip,
switchMap,
catchError,
debounceTime,
distinctUntilChanged,
} from 'rxjs/operators';
import React from 'react';
import { uniqBy } from 'lodash';
import ReactDOM from 'react-dom';
import deepEqual from 'fast-deep-equal';
import { Filter, uniqFilters } from '@kbn/es-query';
import { EMPTY, merge, pipe, Subject, Subscription } from 'rxjs';
import { EuiContextMenuPanel } from '@elastic/eui';
import {
distinctUntilChanged,
debounceTime,
catchError,
switchMap,
map,
skip,
mapTo,
} from 'rxjs/operators';

import {
withSuspense,
LazyReduxEmbeddableWrapper,
ReduxEmbeddableWrapperPropsWithChildren,
ReduxEmbeddablePackage,
ReduxEmbeddableTools,
SolutionToolbarPopover,
} from '@kbn/presentation-util-plugin/public';
import { OverlayRef } from '@kbn/core/public';
import { DataView } from '@kbn/data-views-plugin/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { Container, EmbeddableFactory } from '@kbn/embeddable-plugin/public';

import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import {
ControlGroupInput,
ControlGroupOutput,
ControlGroupReduxState,
ControlPanelState,
ControlsPanels,
CONTROL_GROUP_TYPE,
Expand All @@ -54,10 +51,6 @@ import { controlGroupReducers } from '../state/control_group_reducers';
import { ControlEmbeddable, ControlInput, ControlOutput } from '../../types';
import { CreateControlButton, CreateControlButtonTypes } from '../editor/create_control';

const ControlGroupReduxWrapper = withSuspense<
ReduxEmbeddableWrapperPropsWithChildren<ControlGroupInput>
>(LazyReduxEmbeddableWrapper);

let flyoutRef: OverlayRef | undefined;
export const setFlyoutRef = (newRef: OverlayRef | undefined) => {
flyoutRef = newRef;
Expand All @@ -77,6 +70,11 @@ export class ControlGroupContainer extends Container<
private relevantDataViewId?: string;
private lastUsedDataViewId?: string;

private reduxEmbeddableTools: ReduxEmbeddableTools<
ControlGroupReduxState,
typeof controlGroupReducers
>;

public setLastUsedDataViewId = (lastUsedDataViewId: string) => {
this.lastUsedDataViewId = lastUsedDataViewId;
};
Expand Down Expand Up @@ -162,17 +160,30 @@ export class ControlGroupContainer extends Container<
);
};

constructor(initialInput: ControlGroupInput, parent?: Container) {
constructor(
reduxEmbeddablePackage: ReduxEmbeddablePackage,
initialInput: ControlGroupInput,
parent?: Container
) {
super(
initialInput,
{ embeddableLoaded: {} },
{ dataViewIds: [], embeddableLoaded: {}, filters: [] },
pluginServices.getServices().controls.getControlFactory,
parent,
ControlGroupChainingSystems[initialInput.chainingSystem]?.getContainerSettings(initialInput)
);

this.recalculateFilters$ = new Subject();

// build redux embeddable tools
this.reduxEmbeddableTools = reduxEmbeddablePackage.createTools<
ControlGroupReduxState,
typeof controlGroupReducers
>({
embeddable: this,
reducers: controlGroupReducers,
});

// when all children are ready setup subscriptions
this.untilReady().then(() => {
this.recalculateDataViews();
Expand Down Expand Up @@ -215,7 +226,7 @@ export class ControlGroupContainer extends Container<
.pipe(
// Embeddables often throw errors into their output streams.
catchError(() => EMPTY),
mapTo(childId)
map(() => childId)
)
)
)
Expand Down Expand Up @@ -261,12 +272,12 @@ export class ControlGroupContainer extends Container<
};

private recalculateDataViews = () => {
const allDataViews: DataView[] = [];
const allDataViewIds: Set<string> = new Set();
Object.values(this.children).map((child) => {
const childOutput = child.getOutput() as ControlOutput;
allDataViews.push(...(childOutput.dataViews ?? []));
const dataViewId = (child.getOutput() as ControlOutput).dataViewId;
if (dataViewId) allDataViewIds.add(dataViewId);
});
this.updateOutput({ dataViews: uniqBy(allDataViews, 'id') });
this.updateOutput({ dataViewIds: Array.from(allDataViewIds) });
};

protected createNewPanelState<TEmbeddableInput extends ControlInput = ControlInput>(
Expand Down Expand Up @@ -354,10 +365,11 @@ export class ControlGroupContainer extends Container<
}
this.domNode = dom;
const ControlsServicesProvider = pluginServices.getContextProvider();
const { Wrapper: ControlGroupReduxWrapper } = this.reduxEmbeddableTools;
ReactDOM.render(
<KibanaThemeProvider theme$={pluginServices.getServices().theme.theme$}>
<ControlsServicesProvider>
<ControlGroupReduxWrapper embeddable={this} reducers={controlGroupReducers}>
<ControlGroupReduxWrapper>
<ControlGroup />
</ControlGroupReduxWrapper>
</ControlsServicesProvider>
Expand All @@ -370,6 +382,7 @@ export class ControlGroupContainer extends Container<
super.destroy();
this.closeAllFlyouts();
this.subscriptions.unsubscribe();
this.reduxEmbeddableTools.cleanup();
if (this.domNode) ReactDOM.unmountComponentAtNode(this.domNode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import { Container, EmbeddableFactoryDefinition } from '@kbn/embeddable-plugin/public';
import { lazyLoadReduxEmbeddablePackage } from '@kbn/presentation-util-plugin/public';
import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common';

import { ControlGroupInput, CONTROL_GROUP_TYPE } from '../types';
Expand Down Expand Up @@ -47,7 +48,8 @@ export class ControlGroupContainerFactory implements EmbeddableFactoryDefinition
}

public create = async (initialInput: ControlGroupInput, parent?: Container) => {
const reduxEmbeddablePackage = await lazyLoadReduxEmbeddablePackage();
const { ControlGroupContainer } = await import('./control_group_container');
return new ControlGroupContainer(initialInput, parent);
return new ControlGroupContainer(reduxEmbeddablePackage, initialInput, parent);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,45 +10,45 @@ import { PayloadAction } from '@reduxjs/toolkit';
import { WritableDraft } from 'immer/dist/types/types-external';

import { ControlWidth } from '../../types';
import { ControlGroupInput } from '../types';
import { ControlGroupInput, ControlGroupReduxState } from '../types';

export const controlGroupReducers = {
setControlStyle: (
state: WritableDraft<ControlGroupInput>,
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<ControlGroupInput['controlStyle']>
) => {
state.controlStyle = action.payload;
state.explicitInput.controlStyle = action.payload;
},
setDefaultControlWidth: (
state: WritableDraft<ControlGroupInput>,
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<ControlGroupInput['defaultControlWidth']>
) => {
state.defaultControlWidth = action.payload;
state.explicitInput.defaultControlWidth = action.payload;
},
setDefaultControlGrow: (
state: WritableDraft<ControlGroupInput>,
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<ControlGroupInput['defaultControlGrow']>
) => {
state.defaultControlGrow = action.payload;
state.explicitInput.defaultControlGrow = action.payload;
},
setControlWidth: (
state: WritableDraft<ControlGroupInput>,
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<{ width: ControlWidth; embeddableId: string }>
) => {
state.panels[action.payload.embeddableId].width = action.payload.width;
state.explicitInput.panels[action.payload.embeddableId].width = action.payload.width;
},
setControlGrow: (
state: WritableDraft<ControlGroupInput>,
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<{ grow: boolean; embeddableId: string }>
) => {
state.panels[action.payload.embeddableId].grow = action.payload.grow;
state.explicitInput.panels[action.payload.embeddableId].grow = action.payload.grow;
},
setControlOrders: (
state: WritableDraft<ControlGroupInput>,
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<{ ids: string[] }>
) => {
action.payload.ids.forEach((id, index) => {
state.panels[id].order = index;
state.explicitInput.panels[id].order = index;
});
},
};
8 changes: 7 additions & 1 deletion src/plugins/controls/public/control_group/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@
*/

import { ContainerOutput } from '@kbn/embeddable-plugin/public';
import { ReduxEmbeddableState } from '@kbn/presentation-util-plugin/public';
import { ControlGroupInput } from '../../common/control_group/types';
import { CommonControlOutput } from '../types';

export type ControlGroupOutput = ContainerOutput & CommonControlOutput;
export type ControlGroupOutput = ContainerOutput &
Omit<CommonControlOutput, 'dataViewId'> & { dataViewIds: string[] };

// public only - redux embeddable state type
export type ControlGroupReduxState = ReduxEmbeddableState<ControlGroupInput, ControlGroupOutput>;

export {
type ControlsPanels,
Expand Down
Loading

0 comments on commit 01fc584

Please sign in to comment.