mirror of
https://github.com/apache/superset.git
synced 2024-09-12 00:29:39 -04:00
[slices] add simple new slice form (#2800)
* initial structure for add new slice page * simplify add slice form * add a test * fix long line * use underscore for template name * fix controls path * fix vis types select
This commit is contained in:
parent
7c28e4eace
commit
5bf40e2256
97
superset/assets/javascripts/addSlice/AddSliceContainer.jsx
Normal file
97
superset/assets/javascripts/addSlice/AddSliceContainer.jsx
Normal file
@ -0,0 +1,97 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Panel, Grid, Row, Col } from 'react-bootstrap';
|
||||
import Select from 'react-virtualized-select';
|
||||
import visTypes from '../explore/stores/visTypes';
|
||||
|
||||
const propTypes = {
|
||||
datasources: PropTypes.arrayOf(PropTypes.shape({
|
||||
label: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
})).isRequired,
|
||||
};
|
||||
|
||||
export default class AddSliceContainer extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const visTypeKeys = Object.keys(visTypes);
|
||||
this.vizTypeOptions = visTypeKeys.map(vt => ({ label: visTypes[vt].label, value: vt }));
|
||||
this.state = {
|
||||
datasourceValue: this.props.datasources[0].value,
|
||||
datasourceId: this.props.datasources[0].value.split('__')[0],
|
||||
datasourceType: this.props.datasources[0].value.split('__')[1],
|
||||
visType: 'table',
|
||||
};
|
||||
}
|
||||
|
||||
exploreUrl() {
|
||||
const baseUrl = `/superset/explore/${this.state.datasourceType}/${this.state.datasourceId}`;
|
||||
const formData = encodeURIComponent(JSON.stringify({ viz_type: this.state.visType }));
|
||||
return `${baseUrl}?form_data=${formData}`;
|
||||
}
|
||||
|
||||
gotoSlice() {
|
||||
window.location.href = this.exploreUrl();
|
||||
}
|
||||
|
||||
changeDatasource(e) {
|
||||
this.setState({
|
||||
datasourceValue: e.value,
|
||||
datasourceId: e.value.split('__')[0],
|
||||
datasourceType: e.value.split('__')[1],
|
||||
});
|
||||
}
|
||||
|
||||
changeSliceName(e) {
|
||||
this.setState({ sliceName: e.target.value });
|
||||
}
|
||||
|
||||
changeVisType(e) {
|
||||
this.setState({ visType: e.value });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="container">
|
||||
<Panel header={<h3>Create a new slice</h3>}>
|
||||
<Grid>
|
||||
<Row>
|
||||
<Col xs={12} sm={6}>
|
||||
<div>
|
||||
<p>Choose a datasource</p>
|
||||
<Select
|
||||
clearable={false}
|
||||
name="select-datasource"
|
||||
onChange={this.changeDatasource.bind(this)}
|
||||
options={this.props.datasources}
|
||||
placeholder="Choose a datasource"
|
||||
value={this.state.datasourceValue}
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<p>Choose a visualization type</p>
|
||||
<Select
|
||||
clearable={false}
|
||||
name="select-vis-type"
|
||||
onChange={this.changeVisType.bind(this)}
|
||||
options={this.vizTypeOptions}
|
||||
placeholder="Choose a visualization type"
|
||||
value={this.state.visType}
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<Button bsStyle="primary" onClick={this.gotoSlice.bind(this)}>
|
||||
Create new slice
|
||||
</Button>
|
||||
<br /><br />
|
||||
</Col>
|
||||
</Row>
|
||||
</Grid>
|
||||
</Panel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddSliceContainer.propTypes = propTypes;
|
14
superset/assets/javascripts/addSlice/index.jsx
Normal file
14
superset/assets/javascripts/addSlice/index.jsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { appSetup } from '../common';
|
||||
import AddSliceContainer from './AddSliceContainer';
|
||||
|
||||
appSetup();
|
||||
|
||||
const addSliceContainer = document.getElementById('js-add-slice-container');
|
||||
const bootstrapData = JSON.parse(addSliceContainer.getAttribute('data-bootstrap'));
|
||||
|
||||
ReactDOM.render(
|
||||
<AddSliceContainer datasources={bootstrapData.datasources} />,
|
||||
addSliceContainer,
|
||||
);
|
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { expect } from 'chai';
|
||||
import { describe, it, beforeEach } from 'mocha';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import Select from 'react-virtualized-select';
|
||||
import AddSliceContainer from '../../../javascripts/addSlice/AddSliceContainer';
|
||||
|
||||
const defaultProps = {
|
||||
datasources: [
|
||||
{ label: 'my first table', value: '1__table' },
|
||||
{ label: 'another great table', value: '2__table' },
|
||||
],
|
||||
};
|
||||
|
||||
describe('AddSliceContainer', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<AddSliceContainer {...defaultProps} />);
|
||||
});
|
||||
|
||||
it('uses table as default visType', () => {
|
||||
expect(wrapper.state().visType).to.equal('table');
|
||||
});
|
||||
|
||||
it('renders 2 selects', () => {
|
||||
expect(wrapper.find(Select)).to.have.lengthOf(2);
|
||||
});
|
||||
|
||||
it('renders a button', () => {
|
||||
expect(wrapper.find(Button)).to.have.lengthOf(1);
|
||||
});
|
||||
|
||||
it('formats explore url', () => {
|
||||
const formattedUrl = '/superset/explore/table/1?form_data=%7B%22viz_type%22%3A%22table%22%7D';
|
||||
expect(wrapper.instance().exploreUrl()).to.equal(formattedUrl);
|
||||
});
|
||||
});
|
@ -14,6 +14,7 @@ const config = {
|
||||
entry: {
|
||||
'css-theme': APP_DIR + '/javascripts/css-theme.js',
|
||||
common: APP_DIR + '/javascripts/common.js',
|
||||
addSlice: ['babel-polyfill', APP_DIR + '/javascripts/addSlice/index.jsx'],
|
||||
dashboard: ['babel-polyfill', APP_DIR + '/javascripts/dashboard/Dashboard.jsx'],
|
||||
explore: ['babel-polyfill', APP_DIR + '/javascripts/explore/index.jsx'],
|
||||
sqllab: ['babel-polyfill', APP_DIR + '/javascripts/SqlLab/index.jsx'],
|
||||
|
19
superset/templates/superset/add_slice.html
Normal file
19
superset/templates/superset/add_slice.html
Normal file
@ -0,0 +1,19 @@
|
||||
{% extends "superset/basic.html" %}
|
||||
|
||||
{% block title %}
|
||||
Add new slice
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div
|
||||
id="js-add-slice-container"
|
||||
data-bootstrap="{{ bootstrap_data }}"
|
||||
></div>
|
||||
{% endblock %}
|
||||
|
||||
{% block tail_js %}
|
||||
{{ super() }}
|
||||
{% with filename="addSlice" %}
|
||||
{% include "superset/partials/_script_tag.html" %}
|
||||
{% endwith %}
|
||||
{% endblock %}
|
@ -372,11 +372,17 @@ class SliceModelView(SupersetModelView, DeleteMixin): # noqa
|
||||
@expose('/add', methods=['GET', 'POST'])
|
||||
@has_access
|
||||
def add(self):
|
||||
flash(__(
|
||||
"To create a new slice, you can open a data source "
|
||||
"through the `Sources` menu, or alter an existing slice "
|
||||
"from the `Slices` menu"), "info")
|
||||
return redirect('/superset/welcome')
|
||||
datasources = ConnectorRegistry.get_all_datasources(db.session)
|
||||
datasources = [
|
||||
{'value': str(d.id) + '__' + d.type, 'label': repr(d)}
|
||||
for d in datasources
|
||||
]
|
||||
return self.render_template(
|
||||
"superset/add_slice.html",
|
||||
bootstrap_data=json.dumps({
|
||||
'datasources': datasources,
|
||||
}),
|
||||
)
|
||||
|
||||
appbuilder.add_view(
|
||||
SliceModelView,
|
||||
|
Loading…
Reference in New Issue
Block a user