mirror of
https://github.com/apache/superset.git
synced 2024-09-17 11:09:47 -04:00
Save modal component for explore v2 (#1612)
* Added specs for SaveModal * Move datasource_id and datasource_name to form_data * Add comments * Deleted redundant fetchDashboard * Replcae has_key for python3 * More react and less jquery * Added alert for save slice * Small changes based on comments * Use react bootstrap
This commit is contained in:
parent
dc25bc6f4d
commit
38e94b9e43
@ -4,9 +4,14 @@ import classnames from 'classnames';
|
|||||||
const propTypes = {
|
const propTypes = {
|
||||||
canAdd: PropTypes.string.isRequired,
|
canAdd: PropTypes.string.isRequired,
|
||||||
onQuery: PropTypes.func.isRequired,
|
onQuery: PropTypes.func.isRequired,
|
||||||
|
onSave: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function QueryAndSaveBtns({ canAdd, onQuery }) {
|
const defaultProps = {
|
||||||
|
onSave: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function QueryAndSaveBtns({ canAdd, onQuery, onSave }) {
|
||||||
const saveClasses = classnames('btn btn-default btn-sm', {
|
const saveClasses = classnames('btn btn-default btn-sm', {
|
||||||
'disabled disabledButton': canAdd !== 'True',
|
'disabled disabledButton': canAdd !== 'True',
|
||||||
});
|
});
|
||||||
@ -21,6 +26,7 @@ export default function QueryAndSaveBtns({ canAdd, onQuery }) {
|
|||||||
className={saveClasses}
|
className={saveClasses}
|
||||||
data-target="#save_modal"
|
data-target="#save_modal"
|
||||||
data-toggle="modal"
|
data-toggle="modal"
|
||||||
|
onClick={onSave}
|
||||||
>
|
>
|
||||||
<i className="fa fa-plus-circle"></i> Save as
|
<i className="fa fa-plus-circle"></i> Save as
|
||||||
</button>
|
</button>
|
||||||
@ -29,3 +35,4 @@ export default function QueryAndSaveBtns({ canAdd, onQuery }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QueryAndSaveBtns.propTypes = propTypes;
|
QueryAndSaveBtns.propTypes = propTypes;
|
||||||
|
QueryAndSaveBtns.defaultProps = defaultProps;
|
||||||
|
@ -155,3 +155,53 @@ export const REMOVE_CHART_ALERT = 'REMOVE_CHART_ALERT';
|
|||||||
export function removeChartAlert() {
|
export function removeChartAlert() {
|
||||||
return { type: REMOVE_CHART_ALERT };
|
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_FAILED, userId };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchDashboards(userId) {
|
||||||
|
return function (dispatch) {
|
||||||
|
const url = '/dashboardmodelviewasync/api/read?_flt_0_owners=' + userId;
|
||||||
|
$.get(url, function (data, status) {
|
||||||
|
if (status === 'success') {
|
||||||
|
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));
|
||||||
|
} else {
|
||||||
|
dispatch(fetchDashboardsFailed(userId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SAVE_SLICE_FAILED = 'SAVE_SLICE_FAILED';
|
||||||
|
export function saveSliceFailed() {
|
||||||
|
return { type: SAVE_SLICE_FAILED };
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
$.get(url, (data, status) => {
|
||||||
|
if (status === 'success') {
|
||||||
|
// Go to new slice url or dashboard url
|
||||||
|
window.location = data;
|
||||||
|
} else {
|
||||||
|
dispatch(saveSliceFailed());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -215,8 +215,4 @@ function mapStateToProps(state) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps() {
|
export default connect(mapStateToProps, () => ({}))(ChartContainer);
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ChartContainer);
|
|
||||||
|
@ -108,8 +108,6 @@ function mapStateToProps(state) {
|
|||||||
alert: state.controlPanelAlert,
|
alert: state.controlPanelAlert,
|
||||||
isDatasourceMetaLoading: state.isDatasourceMetaLoading,
|
isDatasourceMetaLoading: state.isDatasourceMetaLoading,
|
||||||
fields: state.fields,
|
fields: state.fields,
|
||||||
datasource_type: state.datasource_type,
|
|
||||||
form_data: state.viz.form_data,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import * as actions from '../actions/exploreActions';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ChartContainer from './ChartContainer';
|
import ChartContainer from './ChartContainer';
|
||||||
import ControlPanelsContainer from './ControlPanelsContainer';
|
import ControlPanelsContainer from './ControlPanelsContainer';
|
||||||
|
import SaveModal from './SaveModal';
|
||||||
import QueryAndSaveBtns from '../../explore/components/QueryAndSaveBtns';
|
import QueryAndSaveBtns from '../../explore/components/QueryAndSaveBtns';
|
||||||
const $ = require('jquery');
|
const $ = require('jquery');
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ class ExploreViewContainer extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
height: this.getHeight(),
|
height: this.getHeight(),
|
||||||
|
showModal: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +65,10 @@ class ExploreViewContainer extends React.Component {
|
|||||||
this.props.datasource_type, this.props.form_data.datasource, data);
|
this.props.datasource_type, this.props.form_data.datasource, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleModal() {
|
||||||
|
this.setState({ showModal: !this.state.showModal });
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -72,16 +78,26 @@ class ExploreViewContainer extends React.Component {
|
|||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{this.state.showModal &&
|
||||||
|
<SaveModal
|
||||||
|
onHide={this.toggleModal.bind(this)}
|
||||||
|
actions={this.props.actions}
|
||||||
|
form_data={this.props.form_data}
|
||||||
|
datasource_type={this.props.datasource_type}
|
||||||
|
/>
|
||||||
|
}
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-sm-4">
|
<div className="col-sm-4">
|
||||||
<QueryAndSaveBtns
|
<QueryAndSaveBtns
|
||||||
canAdd="True"
|
canAdd="True"
|
||||||
onQuery={this.onQuery.bind(this)}
|
onQuery={this.onQuery.bind(this)}
|
||||||
|
onSave={this.toggleModal.bind(this)}
|
||||||
/>
|
/>
|
||||||
<br /><br />
|
<br /><br />
|
||||||
<ControlPanelsContainer
|
<ControlPanelsContainer
|
||||||
actions={this.props.actions}
|
actions={this.props.actions}
|
||||||
form_data={this.props.form_data}
|
form_data={this.props.form_data}
|
||||||
|
datasource_type={this.props.datasource_type}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-8">
|
<div className="col-sm-8">
|
||||||
@ -112,6 +128,5 @@ function mapDispatchToProps(dispatch) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ControlPanelsContainer };
|
export { ExploreViewContainer };
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ExploreViewContainer);
|
export default connect(mapStateToProps, mapDispatchToProps)(ExploreViewContainer);
|
||||||
|
@ -14,7 +14,11 @@ const propTypes = {
|
|||||||
places: PropTypes.number,
|
places: PropTypes.number,
|
||||||
validators: PropTypes.any,
|
validators: PropTypes.any,
|
||||||
onChange: React.PropTypes.func,
|
onChange: React.PropTypes.func,
|
||||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.bool, PropTypes.array]).isRequired,
|
value: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.number,
|
||||||
|
PropTypes.bool,
|
||||||
|
PropTypes.array]).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
|
241
superset/assets/javascripts/explorev2/components/SaveModal.js
Normal file
241
superset/assets/javascripts/explorev2/components/SaveModal.js
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
/* eslint camel-case: 0 */
|
||||||
|
import React, { PropTypes } from 'react';
|
||||||
|
import $ from 'jquery';
|
||||||
|
import { Modal, Alert, Button, Radio } from 'react-bootstrap';
|
||||||
|
import Select from 'react-select';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
can_edit: PropTypes.bool,
|
||||||
|
onHide: PropTypes.func.isRequired,
|
||||||
|
actions: PropTypes.object.isRequired,
|
||||||
|
form_data: PropTypes.object,
|
||||||
|
datasource_type: PropTypes.string.isRequired,
|
||||||
|
user_id: PropTypes.string.isRequired,
|
||||||
|
dashboards: PropTypes.array.isRequired,
|
||||||
|
alert: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
class SaveModal extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
saveToDashboardId: null,
|
||||||
|
newDashboardName: '',
|
||||||
|
newSliceName: '',
|
||||||
|
dashboards: [],
|
||||||
|
alert: null,
|
||||||
|
action: 'overwrite',
|
||||||
|
addToDash: 'noSave',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.actions.fetchDashboards(this.props.user_id);
|
||||||
|
}
|
||||||
|
onChange(name, event) {
|
||||||
|
switch (name) {
|
||||||
|
case 'newSliceName':
|
||||||
|
this.setState({ newSliceName: event.target.value });
|
||||||
|
break;
|
||||||
|
case 'saveToDashboardId':
|
||||||
|
this.setState({ saveToDashboardId: event.value });
|
||||||
|
this.changeDash('existing');
|
||||||
|
break;
|
||||||
|
case 'newDashboardName':
|
||||||
|
this.setState({ newDashboardName: event.target.value });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changeAction(action) {
|
||||||
|
this.setState({ action });
|
||||||
|
}
|
||||||
|
changeDash(dash) {
|
||||||
|
this.setState({ addToDash: dash });
|
||||||
|
}
|
||||||
|
saveOrOverwrite(gotodash) {
|
||||||
|
this.setState({ alert: null });
|
||||||
|
this.props.actions.removeSaveModalAlert();
|
||||||
|
const params = {};
|
||||||
|
const sliceParams = {};
|
||||||
|
params.datasource_id = this.props.form_data.datasource;
|
||||||
|
params.datasource_type = this.props.datasource_type;
|
||||||
|
params.datasource_name = this.props.form_data.datasource_name;
|
||||||
|
|
||||||
|
let sliceName = null;
|
||||||
|
sliceParams.action = this.state.action;
|
||||||
|
if (sliceParams.action === 'saveas') {
|
||||||
|
sliceName = this.state.newSliceName;
|
||||||
|
if (sliceName === '') {
|
||||||
|
this.setState({ alert: 'Please enter a slice name' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sliceParams.slice_name = sliceName;
|
||||||
|
} else {
|
||||||
|
sliceParams.slice_name = this.props.form_data.slice_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(this.props.form_data).forEach((field) => {
|
||||||
|
if (this.props.form_data[field] !== null && field !== 'slice_name') {
|
||||||
|
params[field] = this.props.form_data[field];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const addToDash = this.state.addToDash;
|
||||||
|
sliceParams.add_to_dash = addToDash;
|
||||||
|
let dashboard = null;
|
||||||
|
switch (addToDash) {
|
||||||
|
case ('existing'):
|
||||||
|
dashboard = this.state.saveToDashboardId;
|
||||||
|
if (!dashboard) {
|
||||||
|
this.setState({ alert: 'Please select a dashboard' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sliceParams.save_to_dashboard_id = dashboard;
|
||||||
|
break;
|
||||||
|
case ('new'):
|
||||||
|
dashboard = this.state.newDashboardName;
|
||||||
|
if (dashboard === '') {
|
||||||
|
this.setState({ alert: 'Please enter a dashboard name' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sliceParams.new_dashboard_name = dashboard;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dashboard = null;
|
||||||
|
}
|
||||||
|
params.V2 = true;
|
||||||
|
sliceParams.goto_dash = gotodash;
|
||||||
|
const baseUrl = '/superset/explore/' +
|
||||||
|
`${this.props.datasource_type}/${this.props.form_data.datasource}/`;
|
||||||
|
const saveUrl = `${baseUrl}?${$.param(params, true)}&${$.param(sliceParams, true)}`;
|
||||||
|
this.props.actions.saveSlice(saveUrl);
|
||||||
|
this.props.onHide();
|
||||||
|
}
|
||||||
|
removeAlert() {
|
||||||
|
if (this.props.alert) {
|
||||||
|
this.props.actions.removeSaveModalAlert();
|
||||||
|
}
|
||||||
|
this.setState({ alert: null });
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
show
|
||||||
|
onHide={this.props.onHide}
|
||||||
|
bsStyle="large"
|
||||||
|
>
|
||||||
|
<Modal.Header closeButton>
|
||||||
|
<Modal.Title>
|
||||||
|
Save A Slice
|
||||||
|
</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
{(this.state.alert || this.props.alert) &&
|
||||||
|
<Alert>
|
||||||
|
{this.state.alert ? this.state.alert : this.props.alert}
|
||||||
|
<i
|
||||||
|
className="fa fa-close pull-right"
|
||||||
|
onClick={this.removeAlert.bind(this)}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
/>
|
||||||
|
</Alert>
|
||||||
|
}
|
||||||
|
<Radio
|
||||||
|
disabled={!this.props.can_edit}
|
||||||
|
checked={this.state.action === 'overwrite'}
|
||||||
|
onChange={this.changeAction.bind(this, 'overwrite')}
|
||||||
|
>
|
||||||
|
{`Overwrite slice ${this.props.form_data.slice_name}`}
|
||||||
|
</Radio>
|
||||||
|
|
||||||
|
<Radio
|
||||||
|
inline
|
||||||
|
checked={this.state.action === 'saveas'}
|
||||||
|
onChange={this.changeAction.bind(this, 'saveas')}
|
||||||
|
> Save as
|
||||||
|
</Radio>
|
||||||
|
<input
|
||||||
|
name="new_slice_name"
|
||||||
|
placeholder="[slice name]"
|
||||||
|
onChange={this.onChange.bind(this, 'newSliceName')}
|
||||||
|
onFocus={this.changeAction.bind(this, 'saveas')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<Radio
|
||||||
|
checked={this.state.addToDash === 'noSave'}
|
||||||
|
onChange={this.changeDash.bind(this, 'noSave')}
|
||||||
|
>
|
||||||
|
Do not add to a dashboard
|
||||||
|
</Radio>
|
||||||
|
|
||||||
|
<Radio
|
||||||
|
inline
|
||||||
|
checked={this.state.addToDash === 'existing'}
|
||||||
|
onChange={this.changeDash.bind(this, 'existing')}
|
||||||
|
>
|
||||||
|
Add slice to existing dashboard
|
||||||
|
</Radio>
|
||||||
|
<Select
|
||||||
|
options={this.props.dashboards}
|
||||||
|
onChange={this.onChange.bind(this, 'saveToDashboardId')}
|
||||||
|
autoSize={false}
|
||||||
|
value={this.state.saveToDashboardId}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Radio
|
||||||
|
inline
|
||||||
|
checked={this.state.addToDash === 'new'}
|
||||||
|
onChange={this.changeDash.bind(this, 'new')}
|
||||||
|
>
|
||||||
|
Add to new dashboard
|
||||||
|
</Radio>
|
||||||
|
<input
|
||||||
|
onChange={this.onChange.bind(this, 'newDashboardName')}
|
||||||
|
onFocus={this.changeDash.bind(this, 'new')}
|
||||||
|
placeholder="[dashboard name]"
|
||||||
|
/>
|
||||||
|
</Modal.Body>
|
||||||
|
|
||||||
|
<Modal.Footer>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
id="btn_modal_save"
|
||||||
|
className="btn pull-left"
|
||||||
|
onClick={this.saveOrOverwrite.bind(this)}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
id="btn_modal_save_goto_dash"
|
||||||
|
className="btn btn-primary pull-left gotodash"
|
||||||
|
disabled={this.state.addToDash === 'noSave'}
|
||||||
|
onClick={this.saveOrOverwrite.bind(this, true)}
|
||||||
|
>
|
||||||
|
Save & go to dashboard
|
||||||
|
</Button>
|
||||||
|
</Modal.Footer>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveModal.propTypes = propTypes;
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
can_edit: state.can_edit,
|
||||||
|
user_id: state.user_id,
|
||||||
|
dashboards: state.dashboards,
|
||||||
|
alert: state.saveModalAlert,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { SaveModal };
|
||||||
|
export default connect(mapStateToProps, () => ({}))(SaveModal);
|
@ -13,10 +13,12 @@ const bootstrapData = JSON.parse(exploreViewContainer.getAttribute('data-bootstr
|
|||||||
import { exploreReducer } from './reducers/exploreReducer';
|
import { exploreReducer } from './reducers/exploreReducer';
|
||||||
|
|
||||||
const bootstrappedState = Object.assign(initialState(bootstrapData.viz.form_data.viz_type), {
|
const bootstrappedState = Object.assign(initialState(bootstrapData.viz.form_data.viz_type), {
|
||||||
|
can_edit: bootstrapData.can_edit,
|
||||||
can_download: bootstrapData.can_download,
|
can_download: bootstrapData.can_download,
|
||||||
datasources: bootstrapData.datasources,
|
datasources: bootstrapData.datasources,
|
||||||
datasource_type: bootstrapData.datasource_type,
|
datasource_type: bootstrapData.datasource_type,
|
||||||
viz: bootstrapData.viz,
|
viz: bootstrapData.viz,
|
||||||
|
user_id: bootstrapData.user_id,
|
||||||
});
|
});
|
||||||
bootstrappedState.viz.form_data.datasource = parseInt(bootstrapData.datasource_id, 10);
|
bootstrappedState.viz.form_data.datasource = parseInt(bootstrapData.datasource_id, 10);
|
||||||
bootstrappedState.viz.form_data.datasource_name = bootstrapData.datasource_name;
|
bootstrappedState.viz.form_data.datasource_name = bootstrapData.datasource_name;
|
||||||
|
@ -27,6 +27,16 @@ export const exploreReducer = function (state, action) {
|
|||||||
[actions.REMOVE_CONTROL_PANEL_ALERT]() {
|
[actions.REMOVE_CONTROL_PANEL_ALERT]() {
|
||||||
return Object.assign({}, state, { controlPanelAlert: null });
|
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_OPTIONS]() {
|
[actions.SET_FIELD_OPTIONS]() {
|
||||||
const newState = Object.assign({}, state);
|
const newState = Object.assign({}, state);
|
||||||
const optionsByFieldName = action.options;
|
const optionsByFieldName = action.options;
|
||||||
@ -66,6 +76,9 @@ export const exploreReducer = function (state, action) {
|
|||||||
newFormData.slice_name = state.viz.form_data.slice_name;
|
newFormData.slice_name = state.viz.form_data.slice_name;
|
||||||
newFormData.viz_type = state.viz.form_data.viz_type;
|
newFormData.viz_type = state.viz.form_data.viz_type;
|
||||||
}
|
}
|
||||||
|
if (action.key === 'viz_type') {
|
||||||
|
newFormData.previous_viz_type = state.viz.form_data.viz_type;
|
||||||
|
}
|
||||||
newFormData[action.key] = action.value ? action.value : (!state.viz.form_data[action.key]);
|
newFormData[action.key] = action.value ? action.value : (!state.viz.form_data[action.key]);
|
||||||
return Object.assign(
|
return Object.assign(
|
||||||
{},
|
{},
|
||||||
@ -99,6 +112,12 @@ export const exploreReducer = function (state, action) {
|
|||||||
[actions.REMOVE_CHART_ALERT]() {
|
[actions.REMOVE_CHART_ALERT]() {
|
||||||
return Object.assign({}, state, { chartAlert: null });
|
return Object.assign({}, state, { chartAlert: null });
|
||||||
},
|
},
|
||||||
|
[actions.SAVE_SLICE_FAILED]() {
|
||||||
|
return Object.assign({}, state, { saveModalAlert: 'Failed to save slice' });
|
||||||
|
},
|
||||||
|
[actions.REMOVE_SAVE_MODAL_ALERT]() {
|
||||||
|
return Object.assign({}, state, { saveModalAlert: null });
|
||||||
|
},
|
||||||
};
|
};
|
||||||
if (action.type in actionHandlers) {
|
if (action.type in actionHandlers) {
|
||||||
return actionHandlers[action.type]();
|
return actionHandlers[action.type]();
|
||||||
|
@ -750,7 +750,7 @@ export const fields = {
|
|||||||
type: 'SelectMultipleSortableField',
|
type: 'SelectMultipleSortableField',
|
||||||
label: 'Metrics',
|
label: 'Metrics',
|
||||||
choices: [],
|
choices: [],
|
||||||
default: null,
|
default: [],
|
||||||
description: 'One or many metrics to display',
|
description: 'One or many metrics to display',
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -758,6 +758,7 @@ export const fields = {
|
|||||||
type: 'SelectMultipleSortableField',
|
type: 'SelectMultipleSortableField',
|
||||||
label: 'Ordering',
|
label: 'Ordering',
|
||||||
choices: [],
|
choices: [],
|
||||||
|
default: [],
|
||||||
description: 'One or many metrics to display',
|
description: 'One or many metrics to display',
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -931,6 +932,7 @@ export const fields = {
|
|||||||
type: 'SelectMultipleSortableField',
|
type: 'SelectMultipleSortableField',
|
||||||
label: 'Group by',
|
label: 'Group by',
|
||||||
choices: [],
|
choices: [],
|
||||||
|
default: [],
|
||||||
description: 'One or many fields to group by',
|
description: 'One or many fields to group by',
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -938,6 +940,7 @@ export const fields = {
|
|||||||
type: 'SelectMultipleSortableField',
|
type: 'SelectMultipleSortableField',
|
||||||
label: 'Columns',
|
label: 'Columns',
|
||||||
choices: [],
|
choices: [],
|
||||||
|
default: [],
|
||||||
description: 'One or many fields to pivot as columns',
|
description: 'One or many fields to pivot as columns',
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -945,6 +948,7 @@ export const fields = {
|
|||||||
type: 'SelectMultipleSortableField',
|
type: 'SelectMultipleSortableField',
|
||||||
label: 'Columns',
|
label: 'Columns',
|
||||||
choices: [],
|
choices: [],
|
||||||
|
default: [],
|
||||||
description: 'Columns to display',
|
description: 'Columns to display',
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1552,6 +1556,7 @@ export const fields = {
|
|||||||
type: 'SelectMultipleSortableField',
|
type: 'SelectMultipleSortableField',
|
||||||
label: 'label',
|
label: 'label',
|
||||||
choices: [],
|
choices: [],
|
||||||
|
default: [],
|
||||||
description: '`count` is COUNT(*) if a group by is used. ' +
|
description: '`count` is COUNT(*) if a group by is used. ' +
|
||||||
'Numerical columns will be aggregated with the aggregator. ' +
|
'Numerical columns will be aggregated with the aggregator. ' +
|
||||||
'Non-numerical columns will be used to label points. ' +
|
'Non-numerical columns will be used to label points. ' +
|
||||||
@ -1704,6 +1709,7 @@ export function defaultViz(vizType) {
|
|||||||
|
|
||||||
export function initialState(vizType = 'table') {
|
export function initialState(vizType = 'table') {
|
||||||
return {
|
return {
|
||||||
|
dashboards: [],
|
||||||
isDatasourceMetaLoading: false,
|
isDatasourceMetaLoading: false,
|
||||||
datasources: null,
|
datasources: null,
|
||||||
datasource_type: null,
|
datasource_type: null,
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { describe, it, beforeEach } from 'mocha';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import { defaultFormData } from '../../../../javascripts/explorev2/stores/store';
|
||||||
|
import { SaveModal } from '../../../../javascripts/explorev2/components/SaveModal';
|
||||||
|
import { Modal, Button, Radio } from 'react-bootstrap';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
can_edit: true,
|
||||||
|
onHide: () => ({}),
|
||||||
|
actions: {
|
||||||
|
saveSlice: sinon.spy(),
|
||||||
|
},
|
||||||
|
form_data: defaultFormData,
|
||||||
|
datasource_id: 1,
|
||||||
|
datasource_name: 'birth_names',
|
||||||
|
datasource_type: 'table',
|
||||||
|
user_id: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('SaveModal', () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = shallow(<SaveModal {...defaultProps} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a Modal with 7 inputs and 2 buttons', () => {
|
||||||
|
expect(wrapper.find(Modal)).to.have.lengthOf(1);
|
||||||
|
expect(wrapper.find('input')).to.have.lengthOf(2);
|
||||||
|
expect(wrapper.find(Button)).to.have.lengthOf(2);
|
||||||
|
expect(wrapper.find(Radio)).to.have.lengthOf(5);
|
||||||
|
});
|
||||||
|
});
|
@ -1456,7 +1456,8 @@ class Superset(BaseSupersetView):
|
|||||||
# TODO use form processing form wtforms
|
# TODO use form processing form wtforms
|
||||||
d = args.to_dict(flat=False)
|
d = args.to_dict(flat=False)
|
||||||
del d['action']
|
del d['action']
|
||||||
del d['previous_viz_type']
|
if 'previous_viz_type' in d:
|
||||||
|
del d['previous_viz_type']
|
||||||
|
|
||||||
as_list = ('metrics', 'groupby', 'columns', 'all_columns',
|
as_list = ('metrics', 'groupby', 'columns', 'all_columns',
|
||||||
'mapbox_label', 'order_by_cols')
|
'mapbox_label', 'order_by_cols')
|
||||||
@ -1515,8 +1516,12 @@ class Superset(BaseSupersetView):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
if request.args.get('goto_dash') == 'true':
|
if request.args.get('goto_dash') == 'true':
|
||||||
|
if request.args.get('V2') == 'true':
|
||||||
|
return dash.url
|
||||||
return redirect(dash.url)
|
return redirect(dash.url)
|
||||||
else:
|
else:
|
||||||
|
if request.args.get('V2') == 'true':
|
||||||
|
return slc.slice_url
|
||||||
return redirect(slc.slice_url)
|
return redirect(slc.slice_url)
|
||||||
|
|
||||||
def save_slice(self, slc):
|
def save_slice(self, slc):
|
||||||
|
Loading…
Reference in New Issue
Block a user