mirror of
https://github.com/apache/superset.git
synced 2024-09-12 00:29:39 -04:00
[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
This commit is contained in:
parent
08b7e891a7
commit
b3107bb603
70
superset/assets/javascripts/explore/actions/chartActions.js
Normal file
70
superset/assets/javascripts/explore/actions/chartActions.js
Normal file
@ -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));
|
||||
};
|
||||
}
|
@ -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 };
|
||||
|
@ -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());
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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)),
|
||||
);
|
||||
|
||||
|
72
superset/assets/javascripts/explore/reducers/chartReducer.js
Normal file
72
superset/assets/javascripts/explore/reducers/chartReducer.js
Normal file
@ -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;
|
||||
}
|
@ -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,17 +24,26 @@ export const exploreReducer = function (state, action) {
|
||||
[actions.SET_DATASOURCE]() {
|
||||
return Object.assign({}, state, { datasource: action.datasource });
|
||||
},
|
||||
[actions.FETCH_DATASOURCES_STARTED]() {
|
||||
return Object.assign({}, state, { isDatasourcesLoading: true });
|
||||
},
|
||||
[actions.FETCH_DATASOURCES_SUCCEEDED]() {
|
||||
return Object.assign({}, state, { isDatasourcesLoading: false });
|
||||
},
|
||||
[actions.FETCH_DATASOURCES_FAILED]() {
|
||||
// todo(alanna) handle failure/error state
|
||||
return Object.assign({}, state,
|
||||
{
|
||||
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.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.SET_FIELD_VALUE]() {
|
||||
const controls = Object.assign({}, state.controls);
|
||||
const control = Object.assign({}, controls[action.controlName]);
|
||||
@ -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;
|
||||
};
|
||||
}
|
||||
|
11
superset/assets/javascripts/explore/reducers/index.js
Normal file
11
superset/assets/javascripts/explore/reducers/index.js
Normal file
@ -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,
|
||||
});
|
@ -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;
|
||||
}
|
@ -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);
|
||||
});
|
||||
});
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user