This is an automated email from the ASF dual-hosted git repository.

maximebeauchemin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new b3107bb  [explore] Split large reducer logic in ExploreViewContainer 
(#3088)
b3107bb is described below

commit b3107bb603d2ddae38fec97d81878b2dae339624
Author: Grace Guo <grace....@airbnb.com>
AuthorDate: Thu Aug 10 14:21:45 2017 -0700

    [explore] Split large reducer logic in ExploreViewContainer (#3088)
    
    * split reducer logic for ExploreViewContainer
    
    * fix saveModal component and unit tests
    
    * revert changes in SaveModal_spec.
    will make another commit just to improve test coverage for SaveModal 
component.
    
    * remove comment-out code
    
    * fix merge confilicts
---
 .../javascripts/explore/actions/chartActions.js    |  70 ++++++++++++
 .../javascripts/explore/actions/exploreActions.js  | 123 +--------------------
 .../explore/actions/saveModalActions.js            |  57 ++++++++++
 .../explore/components/ChartContainer.jsx          |  38 +++----
 .../explore/components/ControlPanelsContainer.jsx  |  10 +-
 .../explore/components/ExploreViewContainer.jsx    |  28 +++--
 .../javascripts/explore/components/SaveModal.jsx   |  26 ++---
 superset/assets/javascripts/explore/index.jsx      |  24 ++--
 .../javascripts/explore/reducers/chartReducer.js   |  72 ++++++++++++
 .../javascripts/explore/reducers/exploreReducer.js | 102 ++++-------------
 .../assets/javascripts/explore/reducers/index.js   |  11 ++
 .../explore/reducers/saveModalReducer.js           |  28 +++++
 .../spec/javascripts/explore/chartActions_spec.js  |  36 ++++++
 .../javascripts/explore/exploreActions_spec.js     |  68 +-----------
 14 files changed, 364 insertions(+), 329 deletions(-)

diff --git a/superset/assets/javascripts/explore/actions/chartActions.js 
b/superset/assets/javascripts/explore/actions/chartActions.js
new file mode 100644
index 0000000..6c9291d
--- /dev/null
+++ b/superset/assets/javascripts/explore/actions/chartActions.js
@@ -0,0 +1,70 @@
+import { getExploreUrl } from '../exploreUtils';
+import { getFormDataFromControls } from '../stores/store';
+import { QUERY_TIMEOUT_THRESHOLD } from '../../constants';
+import { triggerQuery } from './exploreActions';
+
+const $ = window.$ = require('jquery');
+
+export const CHART_UPDATE_STARTED = 'CHART_UPDATE_STARTED';
+export function chartUpdateStarted(queryRequest, latestQueryFormData) {
+  return { type: CHART_UPDATE_STARTED, queryRequest, latestQueryFormData };
+}
+
+export const CHART_UPDATE_SUCCEEDED = 'CHART_UPDATE_SUCCEEDED';
+export function chartUpdateSucceeded(queryResponse) {
+  return { type: CHART_UPDATE_SUCCEEDED, queryResponse };
+}
+
+export const CHART_UPDATE_STOPPED = 'CHART_UPDATE_STOPPED';
+export function chartUpdateStopped(queryRequest) {
+  if (queryRequest) {
+    queryRequest.abort();
+  }
+  return { type: CHART_UPDATE_STOPPED };
+}
+
+export const CHART_UPDATE_TIMEOUT = 'CHART_UPDATE_TIMEOUT';
+export function chartUpdateTimeout(statusText) {
+  return { type: CHART_UPDATE_TIMEOUT, statusText };
+}
+
+export const CHART_UPDATE_FAILED = 'CHART_UPDATE_FAILED';
+export function chartUpdateFailed(queryResponse) {
+  return { type: CHART_UPDATE_FAILED, queryResponse };
+}
+
+export const UPDATE_CHART_STATUS = 'UPDATE_CHART_STATUS';
+export function updateChartStatus(status) {
+  return { type: UPDATE_CHART_STATUS, status };
+}
+
+export const CHART_RENDERING_FAILED = 'CHART_RENDERING_FAILED';
+export function chartRenderingFailed(error) {
+  return { type: CHART_RENDERING_FAILED, error };
+}
+
+export const RUN_QUERY = 'RUN_QUERY';
+export function runQuery(formData, force = false) {
+  return function (dispatch, getState) {
+    const { explore } = getState();
+    const lastQueryFormData = getFormDataFromControls(explore.controls);
+    const url = getExploreUrl(formData, 'json', force);
+    const queryRequest = $.ajax({
+      url,
+      dataType: 'json',
+      success(queryResponse) {
+        dispatch(chartUpdateSucceeded(queryResponse));
+      },
+      error(err) {
+        if (err.statusText === 'timeout') {
+          dispatch(chartUpdateTimeout(err.statusText));
+        } else if (err.statusText !== 'abort') {
+          dispatch(chartUpdateFailed(err.responseJSON));
+        }
+      },
+      timeout: QUERY_TIMEOUT_THRESHOLD,
+    });
+    dispatch(chartUpdateStarted(queryRequest, lastQueryFormData));
+    dispatch(triggerQuery(false));
+  };
+}
diff --git a/superset/assets/javascripts/explore/actions/exploreActions.js 
b/superset/assets/javascripts/explore/actions/exploreActions.js
index d45acd5..32fa3c5 100644
--- a/superset/assets/javascripts/explore/actions/exploreActions.js
+++ b/superset/assets/javascripts/explore/actions/exploreActions.js
@@ -1,6 +1,4 @@
 /* eslint camelcase: 0 */
-import { getExploreUrl } from '../exploreUtils';
-import { QUERY_TIMEOUT_THRESHOLD } from '../../constants';
 
 const $ = window.$ = require('jquery');
 
@@ -37,8 +35,8 @@ export function resetControls() {
 }
 
 export const TRIGGER_QUERY = 'TRIGGER_QUERY';
-export function triggerQuery() {
-  return { type: TRIGGER_QUERY };
+export function triggerQuery(value = true) {
+  return { type: TRIGGER_QUERY, value };
 }
 
 export function fetchDatasourceMetadata(datasourceKey, alsoTriggerQuery = 
false) {
@@ -95,39 +93,6 @@ export function setControlValue(controlName, value, 
validationErrors) {
   return { type: SET_FIELD_VALUE, controlName, value, validationErrors };
 }
 
-export const CHART_UPDATE_STARTED = 'CHART_UPDATE_STARTED';
-export function chartUpdateStarted(queryRequest) {
-  return { type: CHART_UPDATE_STARTED, queryRequest };
-}
-
-export const CHART_UPDATE_SUCCEEDED = 'CHART_UPDATE_SUCCEEDED';
-export function chartUpdateSucceeded(queryResponse) {
-  return { type: CHART_UPDATE_SUCCEEDED, queryResponse };
-}
-
-export const CHART_UPDATE_STOPPED = 'CHART_UPDATE_STOPPED';
-export function chartUpdateStopped(queryRequest) {
-  if (queryRequest) {
-    queryRequest.abort();
-  }
-  return { type: CHART_UPDATE_STOPPED };
-}
-
-export const CHART_UPDATE_TIMEOUT = 'CHART_UPDATE_TIMEOUT';
-export function chartUpdateTimeout(statusText) {
-  return { type: CHART_UPDATE_TIMEOUT, statusText };
-}
-
-export const CHART_UPDATE_FAILED = 'CHART_UPDATE_FAILED';
-export function chartUpdateFailed(queryResponse) {
-  return { type: CHART_UPDATE_FAILED, queryResponse };
-}
-
-export const CHART_RENDERING_FAILED = 'CHART_RENDERING_FAILED';
-export function chartRenderingFailed(error) {
-  return { type: CHART_RENDERING_FAILED, error };
-}
-
 export const UPDATE_EXPLORE_ENDPOINTS = 'UPDATE_EXPLORE_ENDPOINTS';
 export function updateExploreEndpoints(jsonUrl, csvUrl, standaloneUrl) {
   return { type: UPDATE_EXPLORE_ENDPOINTS, jsonUrl, csvUrl, standaloneUrl };
@@ -143,95 +108,11 @@ export function removeChartAlert() {
   return { type: REMOVE_CHART_ALERT };
 }
 
-export const FETCH_DASHBOARDS_SUCCEEDED = 'FETCH_DASHBOARDS_SUCCEEDED';
-export function fetchDashboardsSucceeded(choices) {
-  return { type: FETCH_DASHBOARDS_SUCCEEDED, choices };
-}
-
-export const FETCH_DASHBOARDS_FAILED = 'FETCH_DASHBOARDS_FAILED';
-export function fetchDashboardsFailed(userId) {
-  return { type: FETCH_DASHBOARDS_FAILED, userId };
-}
-
-export function fetchDashboards(userId) {
-  return function (dispatch) {
-    const url = '/dashboardmodelviewasync/api/read?_flt_0_owners=' + userId;
-    $.ajax({
-      type: 'GET',
-      url,
-      success: (data) => {
-        const choices = [];
-        for (let i = 0; i < data.pks.length; i++) {
-          choices.push({ value: data.pks[i], label: 
data.result[i].dashboard_title });
-        }
-        dispatch(fetchDashboardsSucceeded(choices));
-      },
-      error: () => {
-        dispatch(fetchDashboardsFailed(userId));
-      },
-    });
-  };
-}
-
-export const SAVE_SLICE_FAILED = 'SAVE_SLICE_FAILED';
-export function saveSliceFailed() {
-  return { type: SAVE_SLICE_FAILED };
-}
-export const SAVE_SLICE_SUCCESS = 'SAVE_SLICE_SUCCESS';
-export function saveSliceSuccess(data) {
-  return { type: SAVE_SLICE_SUCCESS, data };
-}
-
-export const REMOVE_SAVE_MODAL_ALERT = 'REMOVE_SAVE_MODAL_ALERT';
-export function removeSaveModalAlert() {
-  return { type: REMOVE_SAVE_MODAL_ALERT };
-}
-
-export function saveSlice(url) {
-  return function (dispatch) {
-    return $.get(url, (data, status) => {
-      if (status === 'success') {
-        dispatch(saveSliceSuccess(data));
-      } else {
-        dispatch(saveSliceFailed());
-      }
-    });
-  };
-}
-
 export const UPDATE_CHART_TITLE = 'UPDATE_CHART_TITLE';
 export function updateChartTitle(slice_name) {
   return { type: UPDATE_CHART_TITLE, slice_name };
 }
 
-export const UPDATE_CHART_STATUS = 'UPDATE_CHART_STATUS';
-export function updateChartStatus(status) {
-  return { type: UPDATE_CHART_STATUS, status };
-}
-
-export const RUN_QUERY = 'RUN_QUERY';
-export function runQuery(formData, force = false) {
-  return function (dispatch) {
-    const url = getExploreUrl(formData, 'json', force);
-    const queryRequest = $.ajax({
-      url,
-      dataType: 'json',
-      success(queryResponse) {
-        dispatch(chartUpdateSucceeded(queryResponse));
-      },
-      error(err) {
-        if (err.statusText === 'timeout') {
-          dispatch(chartUpdateTimeout(err.statusText));
-        } else if (err.statusText !== 'abort') {
-          dispatch(chartUpdateFailed(err.responseJSON));
-        }
-      },
-      timeout: QUERY_TIMEOUT_THRESHOLD,
-    });
-    dispatch(chartUpdateStarted(queryRequest));
-  };
-}
-
 export const RENDER_TRIGGERED = 'RENDER_TRIGGERED';
 export function renderTriggered() {
   return { type: RENDER_TRIGGERED };
diff --git a/superset/assets/javascripts/explore/actions/saveModalActions.js 
b/superset/assets/javascripts/explore/actions/saveModalActions.js
new file mode 100644
index 0000000..b111128
--- /dev/null
+++ b/superset/assets/javascripts/explore/actions/saveModalActions.js
@@ -0,0 +1,57 @@
+const $ = window.$ = require('jquery');
+
+export const FETCH_DASHBOARDS_SUCCEEDED = 'FETCH_DASHBOARDS_SUCCEEDED';
+export function fetchDashboardsSucceeded(choices) {
+  return { type: FETCH_DASHBOARDS_SUCCEEDED, choices };
+}
+
+export const FETCH_DASHBOARDS_FAILED = 'FETCH_DASHBOARDS_FAILED';
+export function fetchDashboardsFailed(userId) {
+  return { type: FETCH_DASHBOARDS_FAILED, userId };
+}
+
+export function fetchDashboards(userId) {
+  return function (dispatch) {
+    const url = '/dashboardmodelviewasync/api/read?_flt_0_owners=' + userId;
+    return $.ajax({
+      type: 'GET',
+      url,
+      success: (data) => {
+        const choices = [];
+        for (let i = 0; i < data.pks.length; i++) {
+          choices.push({ value: data.pks[i], label: 
data.result[i].dashboard_title });
+        }
+        dispatch(fetchDashboardsSucceeded(choices));
+      },
+      error: () => {
+        dispatch(fetchDashboardsFailed(userId));
+      },
+    });
+  };
+}
+
+export const SAVE_SLICE_FAILED = 'SAVE_SLICE_FAILED';
+export function saveSliceFailed() {
+  return { type: SAVE_SLICE_FAILED };
+}
+export const SAVE_SLICE_SUCCESS = 'SAVE_SLICE_SUCCESS';
+export function saveSliceSuccess(data) {
+  return { type: SAVE_SLICE_SUCCESS, data };
+}
+
+export const REMOVE_SAVE_MODAL_ALERT = 'REMOVE_SAVE_MODAL_ALERT';
+export function removeSaveModalAlert() {
+  return { type: REMOVE_SAVE_MODAL_ALERT };
+}
+
+export function saveSlice(url) {
+  return function (dispatch) {
+    return $.get(url, (data, status) => {
+      if (status === 'success') {
+        dispatch(saveSliceSuccess(data));
+      } else {
+        dispatch(saveSliceFailed());
+      }
+    });
+  };
+}
diff --git a/superset/assets/javascripts/explore/components/ChartContainer.jsx 
b/superset/assets/javascripts/explore/components/ChartContainer.jsx
index ab2be2f..3e93b9e 100644
--- a/superset/assets/javascripts/explore/components/ChartContainer.jsx
+++ b/superset/assets/javascripts/explore/components/ChartContainer.jsx
@@ -322,29 +322,29 @@ class ChartContainer extends React.PureComponent {
 
 ChartContainer.propTypes = propTypes;
 
-function mapStateToProps(state) {
-  const formData = getFormDataFromControls(state.controls);
+function mapStateToProps({ explore, chart }) {
+  const formData = getFormDataFromControls(explore.controls);
   return {
-    alert: state.chartAlert,
-    can_overwrite: state.can_overwrite,
-    can_download: state.can_download,
-    chartStatus: state.chartStatus,
-    chartUpdateEndTime: state.chartUpdateEndTime,
-    chartUpdateStartTime: state.chartUpdateStartTime,
-    datasource: state.datasource,
-    column_formats: state.datasource ? state.datasource.column_formats : null,
-    containerId: state.slice ? `slice-container-${state.slice.slice_id}` : 
'slice-container',
+    alert: explore.chartAlert,
+    can_overwrite: explore.can_overwrite,
+    can_download: explore.can_download,
+    datasource: explore.datasource,
+    column_formats: explore.datasource ? explore.datasource.column_formats : 
null,
+    containerId: explore.slice ? `slice-container-${explore.slice.slice_id}` : 
'slice-container',
     formData,
-    latestQueryFormData: state.latestQueryFormData,
-    isStarred: state.isStarred,
-    queryResponse: state.queryResponse,
-    slice: state.slice,
-    standalone: state.standalone,
+    isStarred: explore.isStarred,
+    slice: explore.slice,
+    standalone: explore.standalone,
     table_name: formData.datasource_name,
     viz_type: formData.viz_type,
-    triggerRender: state.triggerRender,
-    datasourceType: state.datasource.type,
-    datasourceId: state.datasource_id,
+    triggerRender: explore.triggerRender,
+    datasourceType: explore.datasource.type,
+    datasourceId: explore.datasource_id,
+    chartStatus: chart.chartStatus,
+    chartUpdateEndTime: chart.chartUpdateEndTime,
+    chartUpdateStartTime: chart.chartUpdateStartTime,
+    latestQueryFormData: chart.latestQueryFormData,
+    queryResponse: chart.queryResponse,
   };
 }
 
diff --git 
a/superset/assets/javascripts/explore/components/ControlPanelsContainer.jsx 
b/superset/assets/javascripts/explore/components/ControlPanelsContainer.jsx
index e3b2985..8a8c2d8 100644
--- a/superset/assets/javascripts/explore/components/ControlPanelsContainer.jsx
+++ b/superset/assets/javascripts/explore/components/ControlPanelsContainer.jsx
@@ -96,12 +96,12 @@ class ControlPanelsContainer extends React.Component {
 
 ControlPanelsContainer.propTypes = propTypes;
 
-function mapStateToProps(state) {
+function mapStateToProps({ explore }) {
   return {
-    alert: state.controlPanelAlert,
-    isDatasourceMetaLoading: state.isDatasourceMetaLoading,
-    controls: state.controls,
-    exploreState: state,
+    alert: explore.controlPanelAlert,
+    isDatasourceMetaLoading: explore.isDatasourceMetaLoading,
+    controls: explore.controls,
+    exploreState: explore,
   };
 }
 
diff --git 
a/superset/assets/javascripts/explore/components/ExploreViewContainer.jsx 
b/superset/assets/javascripts/explore/components/ExploreViewContainer.jsx
index ae94f6a..bb96dbc 100644
--- a/superset/assets/javascripts/explore/components/ExploreViewContainer.jsx
+++ b/superset/assets/javascripts/explore/components/ExploreViewContainer.jsx
@@ -8,12 +8,15 @@ import ControlPanelsContainer from './ControlPanelsContainer';
 import SaveModal from './SaveModal';
 import QueryAndSaveBtns from './QueryAndSaveBtns';
 import { getExploreUrl } from '../exploreUtils';
-import * as actions from '../actions/exploreActions';
 import { getFormDataFromControls } from '../stores/store';
+import * as exploreActions from '../actions/exploreActions';
+import * as saveModalActions from '../actions/saveModalActions';
+import * as chartActions from '../actions/chartActions';
 
 const propTypes = {
   actions: PropTypes.object.isRequired,
   datasource_type: PropTypes.string.isRequired,
+  isDatasourceMetaLoading: PropTypes.bool.isRequired,
   chartStatus: PropTypes.string,
   controls: PropTypes.object.isRequired,
   forcedHeight: PropTypes.string,
@@ -85,7 +88,6 @@ class ExploreViewContainer extends React.Component {
     return `${window.innerHeight - navHeight}px`;
   }
 
-
   triggerQueryIfNeeded() {
     if (this.props.triggerQuery && !this.hasErrors()) {
       this.props.actions.runQuery(this.props.form_data);
@@ -172,7 +174,9 @@ class ExploreViewContainer extends React.Component {
             <ControlPanelsContainer
               actions={this.props.actions}
               form_data={this.props.form_data}
+              controls={this.props.controls}
               datasource_type={this.props.datasource_type}
+              isDatasourceMetaLoading={this.props.isDatasourceMetaLoading}
             />
           </div>
           <div className="col-sm-8">
@@ -186,21 +190,23 @@ class ExploreViewContainer extends React.Component {
 
 ExploreViewContainer.propTypes = propTypes;
 
-function mapStateToProps(state) {
-  const form_data = getFormDataFromControls(state.controls);
+function mapStateToProps({ explore, chart }) {
+  const form_data = getFormDataFromControls(explore.controls);
   return {
-    chartStatus: state.chartStatus,
-    datasource_type: state.datasource.type,
-    controls: state.controls,
+    isDatasourceMetaLoading: explore.isDatasourceMetaLoading,
+    datasource_type: explore.datasource.type,
+    controls: explore.controls,
     form_data,
-    standalone: state.standalone,
-    triggerQuery: state.triggerQuery,
-    forcedHeight: state.forced_height,
-    queryRequest: state.queryRequest,
+    standalone: explore.standalone,
+    triggerQuery: explore.triggerQuery,
+    forcedHeight: explore.forced_height,
+    queryRequest: chart.queryRequest,
+    chartStatus: chart.chartStatus,
   };
 }
 
 function mapDispatchToProps(dispatch) {
+  const actions = Object.assign({}, exploreActions, saveModalActions, 
chartActions);
   return {
     actions: bindActionCreators(actions, dispatch),
   };
diff --git a/superset/assets/javascripts/explore/components/SaveModal.jsx 
b/superset/assets/javascripts/explore/components/SaveModal.jsx
index 4dbff36..beb07b2 100644
--- a/superset/assets/javascripts/explore/components/SaveModal.jsx
+++ b/superset/assets/javascripts/explore/components/SaveModal.jsx
@@ -1,10 +1,11 @@
 /* eslint camelcase: 0 */
 import React from 'react';
 import PropTypes from 'prop-types';
-import $ from 'jquery';
+import { connect } from 'react-redux';
+
 import { Modal, Alert, Button, Radio } from 'react-bootstrap';
 import Select from 'react-select';
-import { connect } from 'react-redux';
+import { getExploreUrl } from '../exploreUtils';
 
 const propTypes = {
   can_overwrite: PropTypes.bool,
@@ -102,12 +103,7 @@ class SaveModal extends React.Component {
     }
     sliceParams.goto_dash = gotodash;
 
-    const baseUrl = 
`/superset/explore/${this.props.datasource.type}/${this.props.datasource.id}/`;
-    sliceParams.datasource_name = this.props.datasource.name;
-
-    const saveUrl = `${baseUrl}?form_data=` +
-      `${encodeURIComponent(JSON.stringify(this.props.form_data))}` +
-      `&${$.param(sliceParams, true)}`;
+    const saveUrl = getExploreUrl(this.props.form_data, 'base', false, null, 
sliceParams);
     this.props.actions.saveSlice(saveUrl)
       .then((data) => {
         // Go to new slice url or dashboard url
@@ -234,14 +230,14 @@ class SaveModal extends React.Component {
 
 SaveModal.propTypes = propTypes;
 
-function mapStateToProps(state) {
+function mapStateToProps({ explore, saveModal }) {
   return {
-    datasource: state.datasource,
-    slice: state.slice,
-    can_overwrite: state.can_overwrite,
-    user_id: state.user_id,
-    dashboards: state.dashboards,
-    alert: state.saveModalAlert,
+    datasource: explore.datasource,
+    slice: explore.slice,
+    can_overwrite: explore.can_overwrite,
+    user_id: explore.user_id,
+    dashboards: saveModal.dashboards,
+    alert: explore.saveModalAlert,
   };
 }
 
diff --git a/superset/assets/javascripts/explore/index.jsx 
b/superset/assets/javascripts/explore/index.jsx
index 8d29d9e..2f6e898 100644
--- a/superset/assets/javascripts/explore/index.jsx
+++ b/superset/assets/javascripts/explore/index.jsx
@@ -11,7 +11,8 @@ import AlertsWrapper from '../components/AlertsWrapper';
 import { getControlsState, getFormDataFromControls } from './stores/store';
 import { initJQueryAjax } from '../modules/utils';
 import ExploreViewContainer from './components/ExploreViewContainer';
-import { exploreReducer } from './reducers/exploreReducer';
+import rootReducer from './reducers/index';
+
 import { appSetup } from '../common';
 import './main.css';
 import '../../stylesheets/reactable-pagination.css';
@@ -28,23 +29,30 @@ delete bootstrapData.form_data;
 // Initial state
 const bootstrappedState = Object.assign(
   bootstrapData, {
-    chartStatus: null,
-    chartUpdateEndTime: null,
-    chartUpdateStartTime: now(),
-    dashboards: [],
     controls,
-    latestQueryFormData: getFormDataFromControls(controls),
     filterColumnOpts: [],
     isDatasourceMetaLoading: false,
     isStarred: false,
-    queryResponse: null,
     triggerQuery: true,
     triggerRender: false,
     alert: null,
   },
 );
 
-const store = createStore(exploreReducer, bootstrappedState,
+const initState = {
+  chart: {
+    chartStatus: null,
+    chartUpdateEndTime: null,
+    chartUpdateStartTime: now(),
+    latestQueryFormData: getFormDataFromControls(controls),
+    queryResponse: null,
+  },
+  saveModal: {
+    dashboards: [],
+  },
+  explore: bootstrappedState,
+};
+const store = createStore(rootReducer, initState,
   compose(applyMiddleware(thunk), initEnhancer(false)),
 );
 
diff --git a/superset/assets/javascripts/explore/reducers/chartReducer.js 
b/superset/assets/javascripts/explore/reducers/chartReducer.js
new file mode 100644
index 0000000..c41771b
--- /dev/null
+++ b/superset/assets/javascripts/explore/reducers/chartReducer.js
@@ -0,0 +1,72 @@
+/* eslint camelcase: 0 */
+import { now } from '../../modules/dates';
+import * as actions from '../actions/chartActions';
+import { QUERY_TIMEOUT_THRESHOLD } from '../../constants';
+
+export default function chartReducer(state = {}, action) {
+  const actionHandlers = {
+    [actions.CHART_UPDATE_SUCCEEDED]() {
+      return Object.assign(
+        {},
+        state,
+        {
+          chartStatus: 'success',
+          queryResponse: action.queryResponse,
+        },
+      );
+    },
+    [actions.CHART_UPDATE_STARTED]() {
+      return Object.assign({}, state,
+        {
+          chartStatus: 'loading',
+          chartUpdateEndTime: null,
+          chartUpdateStartTime: now(),
+          queryRequest: action.queryRequest,
+          latestQueryFormData: action.latestQueryFormData,
+        });
+    },
+    [actions.CHART_UPDATE_STOPPED]() {
+      return Object.assign({}, state,
+        {
+          chartStatus: 'stopped',
+          chartAlert: 'Updating chart was stopped',
+        });
+    },
+    [actions.CHART_RENDERING_FAILED]() {
+      return Object.assign({}, state, {
+        chartStatus: 'failed',
+        chartAlert: 'An error occurred while rendering the visualization: ' + 
action.error,
+      });
+    },
+    [actions.CHART_UPDATE_TIMEOUT]() {
+      return Object.assign({}, state, {
+        chartStatus: 'failed',
+        chartAlert: '<strong>Query timeout</strong> - visualization query are 
set to timeout at ' +
+        `${QUERY_TIMEOUT_THRESHOLD / 1000} seconds. ` +
+        'Perhaps your data has grown, your database is under unusual load, ' +
+        'or you are simply querying a data source that is to large to be 
processed within the timeout range. ' +
+        'If that is the case, we recommend that you summarize your data 
further.',
+      });
+    },
+    [actions.CHART_UPDATE_FAILED]() {
+      return Object.assign({}, state, {
+        chartStatus: 'failed',
+        chartAlert: action.queryResponse ? action.queryResponse.error : 
'Network error.',
+        chartUpdateEndTime: now(),
+        queryResponse: action.queryResponse,
+      });
+    },
+    [actions.UPDATE_CHART_STATUS]() {
+      const newState = Object.assign({}, state, { chartStatus: action.status 
});
+      if (action.status === 'success' || action.status === 'failed') {
+        newState.chartUpdateEndTime = now();
+      }
+      return newState;
+    },
+  };
+
+  if (action.type in actionHandlers) {
+    return actionHandlers[action.type]();
+  }
+  return state;
+}
diff --git a/superset/assets/javascripts/explore/reducers/exploreReducer.js 
b/superset/assets/javascripts/explore/reducers/exploreReducer.js
index 96e36e3..bc1072f 100644
--- a/superset/assets/javascripts/explore/reducers/exploreReducer.js
+++ b/superset/assets/javascripts/explore/reducers/exploreReducer.js
@@ -1,23 +1,18 @@
 /* eslint camelcase: 0 */
 import { getControlsState, getFormDataFromControls } from '../stores/store';
 import * as actions from '../actions/exploreActions';
-import { now } from '../../modules/dates';
-import { QUERY_TIMEOUT_THRESHOLD } from '../../constants';
 
-export const exploreReducer = function (state, action) {
+export default function exploreReducer(state = {}, action) {
   const actionHandlers = {
     [actions.TOGGLE_FAVE_STAR]() {
       return Object.assign({}, state, { isStarred: action.isStarred });
     },
-
     [actions.FETCH_DATASOURCE_STARTED]() {
       return Object.assign({}, state, { isDatasourceMetaLoading: true });
     },
-
     [actions.FETCH_DATASOURCE_SUCCEEDED]() {
       return Object.assign({}, state, { isDatasourceMetaLoading: false });
     },
-
     [actions.FETCH_DATASOURCE_FAILED]() {
       // todo(alanna) handle failure/error state
       return Object.assign({}, state,
@@ -29,16 +24,25 @@ export const exploreReducer = function (state, action) {
     [actions.SET_DATASOURCE]() {
       return Object.assign({}, state, { datasource: action.datasource });
     },
-    [actions.REMOVE_CONTROL_PANEL_ALERT]() {
-      return Object.assign({}, state, { controlPanelAlert: null });
+    [actions.FETCH_DATASOURCES_STARTED]() {
+      return Object.assign({}, state, { isDatasourcesLoading: true });
     },
-    [actions.FETCH_DASHBOARDS_SUCCEEDED]() {
-      return Object.assign({}, state, { dashboards: action.choices });
+    [actions.FETCH_DATASOURCES_SUCCEEDED]() {
+      return Object.assign({}, state, { isDatasourcesLoading: false });
     },
-
-    [actions.FETCH_DASHBOARDS_FAILED]() {
+    [actions.FETCH_DATASOURCES_FAILED]() {
+      // todo(alanna) handle failure/error state
       return Object.assign({}, state,
-        { saveModalAlert: `fetching dashboards failed for ${action.userId}` });
+        {
+          isDatasourcesLoading: false,
+          controlPanelAlert: action.error,
+        });
+    },
+    [actions.SET_DATASOURCES]() {
+      return Object.assign({}, state, { datasources: action.datasources });
+    },
+    [actions.REMOVE_CONTROL_PANEL_ALERT]() {
+      return Object.assign({}, state, { controlPanelAlert: null });
     },
     [actions.SET_FIELD_VALUE]() {
       const controls = Object.assign({}, state.controls);
@@ -52,70 +56,11 @@ export const exploreReducer = function (state, action) {
       }
       return Object.assign({}, state, changes);
     },
-    [actions.CHART_UPDATE_SUCCEEDED]() {
-      return Object.assign(
-        {},
-        state,
-        {
-          chartStatus: 'success',
-          queryResponse: action.queryResponse,
-        },
-      );
-    },
-    [actions.CHART_UPDATE_STARTED]() {
-      return Object.assign({}, state,
-        {
-          chartStatus: 'loading',
-          chartUpdateEndTime: null,
-          chartUpdateStartTime: now(),
-          triggerQuery: false,
-          queryRequest: action.queryRequest,
-          latestQueryFormData: getFormDataFromControls(state.controls),
-        });
-    },
-    [actions.CHART_UPDATE_STOPPED]() {
-      return Object.assign({}, state,
-        {
-          chartStatus: 'stopped',
-          chartAlert: 'Updating chart was stopped',
-        });
-    },
-    [actions.CHART_RENDERING_FAILED]() {
-      return Object.assign({}, state, {
-        chartStatus: 'failed',
-        chartAlert: 'An error occurred while rendering the visualization: ' + 
action.error,
-      });
-    },
     [actions.TRIGGER_QUERY]() {
       return Object.assign({}, state, {
-        triggerQuery: true,
+        triggerQuery: action.value,
       });
     },
-    [actions.CHART_UPDATE_TIMEOUT]() {
-      return Object.assign({}, state, {
-        chartStatus: 'failed',
-        chartAlert: '<strong>Query timeout</strong> - visualization query are 
set to timeout at ' +
-        `${QUERY_TIMEOUT_THRESHOLD / 1000} seconds. ` +
-        'Perhaps your data has grown, your database is under unusual load, ' +
-        'or you are simply querying a data source that is to large to be 
processed within the timeout range. ' +
-        'If that is the case, we recommend that you summarize your data 
further.',
-      });
-    },
-    [actions.CHART_UPDATE_FAILED]() {
-      return Object.assign({}, state, {
-        chartStatus: 'failed',
-        chartAlert: action.queryResponse ? action.queryResponse.error : 
'Network error.',
-        chartUpdateEndTime: now(),
-        queryResponse: action.queryResponse,
-      });
-    },
-    [actions.UPDATE_CHART_STATUS]() {
-      const newState = Object.assign({}, state, { chartStatus: action.status 
});
-      if (action.status === 'success' || action.status === 'failed') {
-        newState.chartUpdateEndTime = now();
-      }
-      return newState;
-    },
     [actions.UPDATE_CHART_TITLE]() {
       const updatedSlice = Object.assign({}, state.slice, { slice_name: 
action.slice_name });
       return Object.assign({}, state, { slice: updatedSlice });
@@ -126,15 +71,6 @@ export const exploreReducer = function (state, action) {
       }
       return state;
     },
-    [actions.SAVE_SLICE_FAILED]() {
-      return Object.assign({}, state, { saveModalAlert: 'Failed to save slice' 
});
-    },
-    [actions.SAVE_SLICE_SUCCESS](data) {
-      return Object.assign({}, state, { data });
-    },
-    [actions.REMOVE_SAVE_MODAL_ALERT]() {
-      return Object.assign({}, state, { saveModalAlert: null });
-    },
     [actions.RESET_FIELDS]() {
       const controls = getControlsState(state, 
getFormDataFromControls(state.controls));
       return Object.assign({}, state, { controls });
@@ -147,4 +83,4 @@ export const exploreReducer = function (state, action) {
     return actionHandlers[action.type]();
   }
   return state;
-};
+}
diff --git a/superset/assets/javascripts/explore/reducers/index.js 
b/superset/assets/javascripts/explore/reducers/index.js
new file mode 100644
index 0000000..0d5acb0
--- /dev/null
+++ b/superset/assets/javascripts/explore/reducers/index.js
@@ -0,0 +1,11 @@
+import { combineReducers } from 'redux';
+
+import chart from './chartReducer';
+import saveModal from './saveModalReducer';
+import explore from './exploreReducer';
+
+export default combineReducers({
+  chart,
+  saveModal,
+  explore,
+});
diff --git a/superset/assets/javascripts/explore/reducers/saveModalReducer.js 
b/superset/assets/javascripts/explore/reducers/saveModalReducer.js
new file mode 100644
index 0000000..912d531
--- /dev/null
+++ b/superset/assets/javascripts/explore/reducers/saveModalReducer.js
@@ -0,0 +1,28 @@
+/* eslint camelcase: 0 */
+import * as actions from '../actions/saveModalActions';
+
+export default function saveModalReducer(state = {}, action) {
+  const actionHandlers = {
+    [actions.FETCH_DASHBOARDS_SUCCEEDED]() {
+      return Object.assign({}, state, { dashboards: action.choices });
+    },
+    [actions.FETCH_DASHBOARDS_FAILED]() {
+      return Object.assign({}, state,
+        { saveModalAlert: `fetching dashboards failed for ${action.userId}` });
+    },
+    [actions.SAVE_SLICE_FAILED]() {
+      return Object.assign({}, state, { saveModalAlert: 'Failed to save slice' 
});
+    },
+    [actions.SAVE_SLICE_SUCCESS](data) {
+      return Object.assign({}, state, { data });
+    },
+    [actions.REMOVE_SAVE_MODAL_ALERT]() {
+      return Object.assign({}, state, { saveModalAlert: null });
+    },
+  };
+
+  if (action.type in actionHandlers) {
+    return actionHandlers[action.type]();
+  }
+  return state;
+}
diff --git a/superset/assets/spec/javascripts/explore/chartActions_spec.js 
b/superset/assets/spec/javascripts/explore/chartActions_spec.js
new file mode 100644
index 0000000..b2e069a
--- /dev/null
+++ b/superset/assets/spec/javascripts/explore/chartActions_spec.js
@@ -0,0 +1,36 @@
+import { it, describe } from 'mocha';
+import { expect } from 'chai';
+import sinon from 'sinon';
+import $ from 'jquery';
+import * as exploreUtils from '../../../javascripts/explore/exploreUtils';
+import * as actions from '../../../javascripts/explore/actions/chartActions';
+
+describe('chart actions', () => {
+  let dispatch;
+  let urlStub;
+  let ajaxStub;
+  let request;
+
+  beforeEach(() => {
+    dispatch = sinon.spy();
+    urlStub = sinon.stub(exploreUtils, 'getExploreUrl').callsFake(() => 
('mockURL'));
+    ajaxStub = sinon.stub($, 'ajax');
+  });
+
+  afterEach(() => {
+    urlStub.restore();
+    ajaxStub.restore();
+  });
+
+  it('should handle query timeout', () => {
+    ajaxStub.yieldsTo('error', { statusText: 'timeout' });
+    request = actions.runQuery({});
+    request(dispatch, sinon.stub().returns({
+      explore: {
+        controls: [],
+      },
+    }));
+    expect(dispatch.callCount).to.equal(3);
+    expect(dispatch.args[0][0].type).to.equal(actions.CHART_UPDATE_TIMEOUT);
+  });
+});
diff --git a/superset/assets/spec/javascripts/explore/exploreActions_spec.js 
b/superset/assets/spec/javascripts/explore/exploreActions_spec.js
index 9fa02e4..5d2926d 100644
--- a/superset/assets/spec/javascripts/explore/exploreActions_spec.js
+++ b/superset/assets/spec/javascripts/explore/exploreActions_spec.js
@@ -4,9 +4,8 @@ import { expect } from 'chai';
 import sinon from 'sinon';
 import $ from 'jquery';
 import * as actions from '../../../javascripts/explore/actions/exploreActions';
-import * as exploreUtils from '../../../javascripts/explore/exploreUtils';
 import { defaultState } from '../../../javascripts/explore/stores/store';
-import { exploreReducer } from 
'../../../javascripts/explore/reducers/exploreReducer';
+import exploreReducer from 
'../../../javascripts/explore/reducers/exploreReducer';
 
 describe('reducers', () => {
   it('sets correct control value given a key and value', () => {
@@ -81,69 +80,4 @@ describe('fetching actions', () => {
       expect(dispatch.getCall(4).args[0].type).to.equal(actions.TRIGGER_QUERY);
     });
   });
-
-  describe('fetchDashboards', () => {
-    const userID = 1;
-    const mockDashboardData = {
-      pks: ['value'],
-      result: [
-        { dashboard_title: 'dashboard title' },
-      ],
-    };
-    const makeRequest = () => {
-      request = actions.fetchDashboards(userID);
-      request(dispatch);
-    };
-
-    it('makes the ajax request', () => {
-      makeRequest();
-      expect(ajaxStub.calledOnce).to.be.true;
-    });
-
-    it('calls correct url', () => {
-      const url = '/dashboardmodelviewasync/api/read?_flt_0_owners=' + userID;
-      makeRequest();
-      expect(ajaxStub.getCall(0).args[0].url).to.equal(url);
-    });
-
-    it('calls correct actions on error', () => {
-      ajaxStub.yieldsTo('error', { responseJSON: { error: 'error text' } });
-      makeRequest();
-      expect(dispatch.callCount).to.equal(1);
-      
expect(dispatch.getCall(0).args[0].type).to.equal(actions.FETCH_DASHBOARDS_FAILED);
-    });
-
-    it('calls correct actions on success', () => {
-      ajaxStub.yieldsTo('success', mockDashboardData);
-      makeRequest();
-      expect(dispatch.callCount).to.equal(1);
-      
expect(dispatch.getCall(0).args[0].type).to.equal(actions.FETCH_DASHBOARDS_SUCCEEDED);
-    });
-  });
-});
-
-describe('runQuery', () => {
-  let dispatch;
-  let urlStub;
-  let ajaxStub;
-  let request;
-
-  beforeEach(() => {
-    dispatch = sinon.spy();
-    urlStub = sinon.stub(exploreUtils, 'getExploreUrl').callsFake(() => 
('mockURL'));
-    ajaxStub = sinon.stub($, 'ajax');
-  });
-
-  afterEach(() => {
-    urlStub.restore();
-    ajaxStub.restore();
-  });
-
-  it('should handle query timeout', () => {
-    ajaxStub.yieldsTo('error', { statusText: 'timeout' });
-    request = actions.runQuery({});
-    request(dispatch);
-    expect(dispatch.callCount).to.equal(2);
-    expect(dispatch.args[0][0].type).to.equal(actions.CHART_UPDATE_TIMEOUT);
-  });
 });

-- 
To stop receiving notification emails like this one, please contact
['"comm...@superset.apache.org" <comm...@superset.apache.org>'].

Reply via email to