diff --git a/superset-frontend/spec/javascripts/explore/controlUtils_spec.jsx b/superset-frontend/spec/javascripts/explore/controlUtils_spec.jsx
index 946517d89f9b..41b7241adf46 100644
--- a/superset-frontend/spec/javascripts/explore/controlUtils_spec.jsx
+++ b/superset-frontend/spec/javascripts/explore/controlUtils_spec.jsx
@@ -27,6 +27,7 @@ import {
getFormDataFromControls,
applyMapStateToPropsToControl,
getAllControlsState,
+ getControlsState,
} from 'src/explore/controlUtils';
describe('controlUtils', () => {
@@ -248,6 +249,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', () => {
diff --git a/superset-frontend/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx b/superset-frontend/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx
index 82bf912bc56d..95511ea6e16e 100644
--- a/superset-frontend/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx
+++ b/superset-frontend/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx
@@ -201,7 +201,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();
diff --git a/superset-frontend/src/explore/controlUtils.js b/superset-frontend/src/explore/controlUtils.js
index 41c5de08e1dd..1219ff78dd6b 100644
--- a/superset-frontend/src/explore/controlUtils.js
+++ b/superset-frontend/src/explore/controlUtils.js
@@ -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
diff --git a/superset-frontend/src/explore/controls.jsx b/superset-frontend/src/explore/controls.jsx
index fc72ed75606c..8b09c9245103 100644
--- a/superset-frontend/src/explore/controls.jsx
+++ b/superset-frontend/src/explore/controls.jsx
@@ -316,7 +316,6 @@ export const controls = {
'filter below is applied against this column or ' +
'expression',
),
- default: control => control.default,
clearable: false,
optionRenderer: c => ,
valueRenderer: c => ,
diff --git a/superset-frontend/src/explore/reducers/getInitialState.js b/superset-frontend/src/explore/reducers/getInitialState.js
index 270ef2644c1b..4efa18dcd431 100644
--- a/superset-frontend/src/explore/reducers/getInitialState.js
+++ b/superset-frontend/src/explore/reducers/getInitialState.js
@@ -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 || [],
diff --git a/superset-frontend/src/explore/store.js b/superset-frontend/src/explore/store.js
index 1e54901f499a..db456f71213c 100644
--- a/superset-frontend/src/explore/store.js
+++ b/superset-frontend/src/explore/store.js
@@ -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';