Skip to content

Commit

Permalink
Track Input Output and componentState in redux for options list, rang…
Browse files Browse the repository at this point in the history
…e slider, and control group
  • Loading branch information
ThomThomson committed Jul 18, 2022
1 parent 0e00f73 commit ffa8253
Show file tree
Hide file tree
Showing 28 changed files with 320 additions and 210 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 @@ -19,7 +19,7 @@ export interface OptionsListEmbeddableInput extends DataControlInput {
singleSelect?: 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,
ReduxEmbeddableTools,
createReduxEmbeddableTools,
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 @@ -165,14 +163,23 @@ export class ControlGroupContainer extends Container<
constructor(initialInput: ControlGroupInput, parent?: Container) {
super(
initialInput,
{ embeddableLoaded: {} },
{ dataViewIds: [], loading: false, embeddableLoaded: {}, filters: [] },
pluginServices.getServices().controls.getControlFactory,
parent,
ControlGroupChainingSystems[initialInput.chainingSystem]?.getContainerSettings(initialInput)
);

this.recalculateFilters$ = new Subject();

// build redux embeddable tools
this.reduxEmbeddableTools = createReduxEmbeddableTools<
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 +222,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 +268,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 +361,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 +378,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 @@ -17,38 +17,38 @@ export const controlGroupReducers = {
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<ControlGroupInput['controlStyle']>
) => {
state.input.controlStyle = action.payload;
state.explicitInput.controlStyle = action.payload;
},
setDefaultControlWidth: (
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<ControlGroupInput['defaultControlWidth']>
) => {
state.input.defaultControlWidth = action.payload;
state.explicitInput.defaultControlWidth = action.payload;
},
setDefaultControlGrow: (
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<ControlGroupInput['defaultControlGrow']>
) => {
state.input.defaultControlGrow = action.payload;
state.explicitInput.defaultControlGrow = action.payload;
},
setControlWidth: (
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<{ width: ControlWidth; embeddableId: string }>
) => {
state.input.panels[action.payload.embeddableId].width = action.payload.width;
state.explicitInput.panels[action.payload.embeddableId].width = action.payload.width;
},
setControlGrow: (
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<{ grow: boolean; embeddableId: string }>
) => {
state.input.panels[action.payload.embeddableId].grow = action.payload.grow;
state.explicitInput.panels[action.payload.embeddableId].grow = action.payload.grow;
},
setControlOrders: (
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<{ ids: string[] }>
) => {
action.payload.ids.forEach((id, index) => {
state.input.panels[id].order = index;
state.explicitInput.panels[id].order = index;
});
},
};
3 changes: 2 additions & 1 deletion src/plugins/controls/public/control_group/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ 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>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@ export const OptionsListComponent = ({
const dispatch = useEmbeddableDispatch();

// Select current state from Redux using multiple selectors to avoid rerenders.
const invalidSelections = select((state) => state.componentState?.invalidSelections);
const validSelections = select((state) => state.componentState?.validSelections);
const selectedOptions = select((state) => state.input.selectedOptions);
const controlStyle = select((state) => state.input.controlStyle);
const singleSelect = select((state) => state.input.singleSelect);
const invalidSelections = select((state) => state.componentState.invalidSelections);
const validSelections = select((state) => state.componentState.validSelections);

const selectedOptions = select((state) => state.explicitInput.selectedOptions);
const controlStyle = select((state) => state.explicitInput.controlStyle);
const singleSelect = select((state) => state.explicitInput.singleSelect);
const id = select((state) => state.explicitInput.id);

const loading = select((state) => state.output.loading);
const id = select((state) => state.input.id);

// debounce loading state so loading doesn't flash when user types
const [buttonLoading, setButtonLoading] = useState(true);
Expand Down
Loading

0 comments on commit ffa8253

Please sign in to comment.