diff --git a/superset/assets/javascripts/addSlice/AddSliceContainer.jsx b/superset/assets/javascripts/addSlice/AddSliceContainer.jsx
new file mode 100644
index 0000000000..2263e20e8d
--- /dev/null
+++ b/superset/assets/javascripts/addSlice/AddSliceContainer.jsx
@@ -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 (
+
+
Create a new slice}>
+
+
+
+
+
Choose a datasource
+
+
+
+
+
Choose a visualization type
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+AddSliceContainer.propTypes = propTypes;
diff --git a/superset/assets/javascripts/addSlice/index.jsx b/superset/assets/javascripts/addSlice/index.jsx
new file mode 100644
index 0000000000..f83c2d5272
--- /dev/null
+++ b/superset/assets/javascripts/addSlice/index.jsx
@@ -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,
+);
diff --git a/superset/assets/spec/javascripts/addSlice/AddSliceContainer_spec.jsx b/superset/assets/spec/javascripts/addSlice/AddSliceContainer_spec.jsx
new file mode 100644
index 0000000000..9096ba3819
--- /dev/null
+++ b/superset/assets/spec/javascripts/addSlice/AddSliceContainer_spec.jsx
@@ -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();
+ });
+
+ 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);
+ });
+});
diff --git a/superset/assets/webpack.config.js b/superset/assets/webpack.config.js
index 73bcb0ace5..2f51c8c515 100644
--- a/superset/assets/webpack.config.js
+++ b/superset/assets/webpack.config.js
@@ -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'],
diff --git a/superset/templates/superset/add_slice.html b/superset/templates/superset/add_slice.html
new file mode 100644
index 0000000000..c2e6978eb5
--- /dev/null
+++ b/superset/templates/superset/add_slice.html
@@ -0,0 +1,19 @@
+{% extends "superset/basic.html" %}
+
+{% block title %}
+ Add new slice
+{% endblock %}
+
+{% block body %}
+
+{% endblock %}
+
+{% block tail_js %}
+ {{ super() }}
+ {% with filename="addSlice" %}
+ {% include "superset/partials/_script_tag.html" %}
+ {% endwith %}
+{% endblock %}
diff --git a/superset/views/core.py b/superset/views/core.py
index 802b891988..109e6b6850 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -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,