[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:
Alanna Scott 2017-06-07 22:08:59 -07:00 committed by GitHub
parent 7c28e4eace
commit 5bf40e2256
6 changed files with 181 additions and 5 deletions

View 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;

View 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,
);

View File

@ -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);
});
});

View File

@ -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'],

View 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 %}

View File

@ -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,