fix: optimize mapStateToProps for chart controls (#10264)

This commit is contained in:
Jesse Yang 2020-07-09 00:33:48 -07:00 committed by GitHub
parent 8d9bb5f472
commit e94c9804a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 63 additions and 28 deletions

View File

@ -27,6 +27,7 @@ import {
getFormDataFromControls,
applyMapStateToPropsToControl,
getAllControlsState,
getControlsState,
} from 'src/explore/controlUtils';
describe('controlUtils', () => {
@ -246,6 +247,14 @@ describe('controlUtils', () => {
const control = getControlState('metrics', 'table', stateWithCount);
expect(control.default).toEqual(['count']);
});
it('should not apply mapStateToProps when initializing', () => {
const control = getControlState('metrics', 'table', {
...state,
isInitializing: true,
});
expect(control.default).toEqual(null);
});
});
describe('validateControl', () => {

View File

@ -199,7 +199,6 @@ describe('ExploreResultsButton', () => {
const calls = fetchMock.calls(visualizeEndpoint);
expect(calls).toHaveLength(1);
const formData = calls[0][1].body;
Object.keys(mockOptions).forEach(key => {
// eslint-disable-next-line no-unused-expressions
expect(formData.get(key)).toBeDefined();

View File

@ -88,15 +88,15 @@ export const getControlConfig = memoizeOne(function getControlConfig(
return control?.config || control;
});
export function applyMapStateToPropsToControl(control, state) {
if (control.mapStateToProps) {
const appliedControl = { ...control };
if (state) {
Object.assign(appliedControl, control.mapStateToProps(state, control));
}
return appliedControl;
export function applyMapStateToPropsToControl(controlState, controlPanelState) {
const { mapStateToProps } = controlState;
if (mapStateToProps && controlPanelState) {
return {
...controlState,
...mapStateToProps(controlPanelState, controlState),
};
}
return control;
return controlState;
}
function handleMissingChoice(control) {
@ -121,19 +121,37 @@ function handleMissingChoice(control) {
return control;
}
export function getControlStateFromControlConfig(controlConfig, state, value) {
export function getControlStateFromControlConfig(
controlConfig,
controlPanelState,
value,
) {
// skip invalid config values
if (!controlConfig) {
return null;
}
const controlState = applyMapStateToPropsToControl(
{ ...controlConfig },
state,
);
let controlState = { ...controlConfig };
// only apply mapStateToProps when control states have been initialized
if (
controlPanelState &&
(controlPanelState.controls || !controlPanelState.isInitializing)
) {
controlState = applyMapStateToPropsToControl(
controlState,
controlPanelState,
);
}
// If default is a function, evaluate it
if (typeof controlState.default === 'function') {
controlState.default = controlState.default(controlState);
controlState.default = controlState.default(
controlState,
controlPanelState,
);
// if default is still a function, discard
if (typeof controlState.default === 'function') {
delete controlState.default;
}
}
// If a choice control went from multi=false to true, wrap value in array

View File

@ -316,7 +316,6 @@ export const controls = {
'filter below is applied against this column or ' +
'expression',
),
default: control => control.default,
clearable: false,
optionRenderer: c => <ColumnOption column={c} showType />,
valueRenderer: c => <ColumnOption column={c} />,

View File

@ -21,27 +21,41 @@ import shortid from 'shortid';
import getToastsFromPyFlashMessages from '../../messageToasts/utils/getToastsFromPyFlashMessages';
import { getChartKey } from '../exploreUtils';
import { getControlsState } from '../store';
import { getFormDataFromControls } from '../controlUtils';
import {
getFormDataFromControls,
applyMapStateToPropsToControl,
} from '../controlUtils';
export default function getInitialState(bootstrapData) {
const controls = getControlsState(bootstrapData, bootstrapData.form_data);
const rawFormData = { ...bootstrapData.form_data };
const { form_data: rawFormData } = bootstrapData;
const slice = bootstrapData.slice;
const sliceName = slice ? slice.slice_name : null;
const bootstrappedState = {
...bootstrapData,
sliceName,
common: {
flash_messages: bootstrapData.common.flash_messages,
conf: bootstrapData.common.conf,
},
rawFormData,
controls,
filterColumnOpts: [],
isDatasourceMetaLoading: false,
isStarred: false,
isInitializing: true,
};
const controls = getControlsState(bootstrappedState, rawFormData);
bootstrappedState.controls = controls;
const slice = bootstrappedState.slice;
const sliceName = slice ? slice.slice_name : null;
// apply initial mapStateToProps for all controls, must execute AFTER
// bootstrappedState has initialized `controls`. Order of execution is not
// guaranteed, so controls shouldn't rely on the each other's mapped state.
Object.entries(controls).forEach(([key, controlState]) => {
controls[key] = applyMapStateToPropsToControl(
controlState,
bootstrappedState,
);
});
bootstrappedState.isInitializing = false;
const sliceFormData = slice
? getFormDataFromControls(getControlsState(bootstrapData, slice.form_data))
@ -69,10 +83,7 @@ export default function getInitialState(bootstrapData) {
dashboards: [],
saveModalAlert: null,
},
explore: {
...bootstrappedState,
sliceName,
},
explore: bootstrappedState,
impressionId: shortid.generate(),
messageToasts: getToastsFromPyFlashMessages(
(bootstrapData.common || {}).flash_messages || [],

View File

@ -39,7 +39,6 @@ export function getControlsState(state, inputFormData) {
* adds value keys coming from inputFormData passed here. This can't be an action creator
* just yet because it's used in both the explore and dashboard views.
* */
// Getting a list of active control names for the current viz
const formData = { ...inputFormData };
const vizType = formData.viz_type || 'table';