From ae302ab32a5d58e11a699f9b263421530a68b6f7 Mon Sep 17 00:00:00 2001 From: alanna scott Date: Fri, 21 Apr 2017 17:18:14 -0700 Subject: [PATCH 1/7] initial structure for add new slice page --- .../addSlice/AddSliceContainer.jsx | 28 +++++++++++++++++++ .../assets/javascripts/addSlice/index.jsx | 11 ++++++++ superset/assets/webpack.config.js | 1 + superset/templates/superset/add-slice.html | 19 +++++++++++++ superset/views/core.py | 9 +++--- 5 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 superset/assets/javascripts/addSlice/AddSliceContainer.jsx create mode 100644 superset/assets/javascripts/addSlice/index.jsx create mode 100644 superset/templates/superset/add-slice.html diff --git a/superset/assets/javascripts/addSlice/AddSliceContainer.jsx b/superset/assets/javascripts/addSlice/AddSliceContainer.jsx new file mode 100644 index 0000000000000..1b1f59270e7c6 --- /dev/null +++ b/superset/assets/javascripts/addSlice/AddSliceContainer.jsx @@ -0,0 +1,28 @@ +import React from 'react'; +import visTypes from '../exploreV2/stores/visTypes'; +import $ from 'jquery'; + +export default class AddSliceContainer extends React.Component { + saveSlice() { + const baseUrl = `/superset/explore/table/1/`; + const formData = { + viz_type: 'markup', + }; + const params = { + slice_name: 'my great slice', + }; + const exploreUrl = `${baseUrl}?form_data=` + + `${encodeURIComponent(JSON.stringify(formData))}` + + `&${$.param(params, true)}`; + console.log('exploreUrl', exploreUrl) + window.location.href = exploreUrl; + } + render() { + console.log('this.props', this.props) + return ( +
+ +
+ ); + } +} diff --git a/superset/assets/javascripts/addSlice/index.jsx b/superset/assets/javascripts/addSlice/index.jsx new file mode 100644 index 0000000000000..48a15339ec883 --- /dev/null +++ b/superset/assets/javascripts/addSlice/index.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import AddSliceContainer from './AddSliceContainer'; + +const addSliceContainer = document.getElementById('js-add-slice-container'); +const bootstrapData = JSON.parse(addSliceContainer.getAttribute('data-bootstrap')); + +ReactDOM.render( + , + addSliceContainer, +); diff --git a/superset/assets/webpack.config.js b/superset/assets/webpack.config.js index 73bcb0ace5f60..2f51c8c5150e3 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 0000000000000..c2e6978eb5824 --- /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 802b8919887a9..55e41d1538c01 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -372,11 +372,10 @@ def pre_delete(self, obj): @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') + return self.render_template( + "superset/add-slice.html", + bootstrap_data=json.dumps({'data': 'data'}), + ) appbuilder.add_view( SliceModelView, From 038760b4f6b27c5b563a8e98b685b09c8f8cd401 Mon Sep 17 00:00:00 2001 From: alanna scott Date: Tue, 23 May 2017 00:19:27 -0700 Subject: [PATCH 2/7] simplify add slice form --- .../addSlice/AddSliceContainer.jsx | 104 +++++++++++++++--- .../assets/javascripts/addSlice/index.jsx | 5 +- superset/views/core.py | 8 +- 3 files changed, 97 insertions(+), 20 deletions(-) diff --git a/superset/assets/javascripts/addSlice/AddSliceContainer.jsx b/superset/assets/javascripts/addSlice/AddSliceContainer.jsx index 1b1f59270e7c6..693f166bcb571 100644 --- a/superset/assets/javascripts/addSlice/AddSliceContainer.jsx +++ b/superset/assets/javascripts/addSlice/AddSliceContainer.jsx @@ -1,28 +1,96 @@ import React from 'react'; -import visTypes from '../exploreV2/stores/visTypes'; -import $ from 'jquery'; - -export default class AddSliceContainer extends React.Component { - saveSlice() { - const baseUrl = `/superset/explore/table/1/`; - const formData = { - viz_type: 'markup', - }; - const params = { - slice_name: 'my great slice', +import PropTypes from 'prop-types'; +import { Button, Panel, Grid, Row, Col } from 'react-bootstrap'; +import Select from 'react-virtualized-select'; + +import controls from '../explorev2/stores/controls'; + +const propTypes = { + datasources: PropTypes.arrayOf(PropTypes.shape({ + label: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + })).isRequired, +}; + +class AddSliceContainer extends React.Component { + constructor(props) { + super(props); + this.vizTypeOptions = controls.viz_type.choices.map(vt => ({ label: vt[1], value: vt[0] })); + 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', }; - const exploreUrl = `${baseUrl}?form_data=` + - `${encodeURIComponent(JSON.stringify(formData))}` + - `&${$.param(params, true)}`; - console.log('exploreUrl', exploreUrl) + } + + gotoSlice() { + const baseUrl = `/superset/explore/${this.state.datasourceType}/${this.state.datasourceId}`; + const formData = encodeURIComponent(JSON.stringify({ viz_type: this.state.visType })); + const exploreUrl = `${baseUrl}?form_data=${formData}`; window.location.href = 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() { - console.log('this.props', this.props) return ( -
- +
+ Create a new slice}> + + + +
+

Choose a datasource

+ +
+
+ +

+ +
+
+
); } } + +AddSliceContainer.propTypes = propTypes; + +export default AddSliceContainer; diff --git a/superset/assets/javascripts/addSlice/index.jsx b/superset/assets/javascripts/addSlice/index.jsx index 48a15339ec883..f83c2d5272a49 100644 --- a/superset/assets/javascripts/addSlice/index.jsx +++ b/superset/assets/javascripts/addSlice/index.jsx @@ -1,11 +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/views/core.py b/superset/views/core.py index 55e41d1538c01..d3aa68ac7249a 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -372,9 +372,15 @@ def pre_delete(self, obj): @expose('/add', methods=['GET', 'POST']) @has_access def add(self): + 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({'data': 'data'}), + bootstrap_data=json.dumps({ + 'datasources': datasources, + }), ) appbuilder.add_view( From 88008d6c7572269e8e4d3435f901b8bf10635e27 Mon Sep 17 00:00:00 2001 From: alanna scott Date: Tue, 23 May 2017 00:43:57 -0700 Subject: [PATCH 3/7] add a test --- .../addSlice/AddSliceContainer.jsx | 9 +++-- .../addSlice/AddSliceContainer_spec.jsx | 39 +++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 superset/assets/spec/javascripts/addSlice/AddSliceContainer_spec.jsx diff --git a/superset/assets/javascripts/addSlice/AddSliceContainer.jsx b/superset/assets/javascripts/addSlice/AddSliceContainer.jsx index 693f166bcb571..d5855a55705aa 100644 --- a/superset/assets/javascripts/addSlice/AddSliceContainer.jsx +++ b/superset/assets/javascripts/addSlice/AddSliceContainer.jsx @@ -24,11 +24,14 @@ class AddSliceContainer extends React.Component { }; } - gotoSlice() { + exploreUrl() { const baseUrl = `/superset/explore/${this.state.datasourceType}/${this.state.datasourceId}`; const formData = encodeURIComponent(JSON.stringify({ viz_type: this.state.visType })); - const exploreUrl = `${baseUrl}?form_data=${formData}`; - window.location.href = exploreUrl; + return `${baseUrl}?form_data=${formData}`; + } + + gotoSlice() { + window.location.href = this.exploreUrl(); } changeDatasource(e) { 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 0000000000000..9096ba38194dd --- /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); + }); +}); From 8bd9a355c971c4c1722e0bb4d817f8a1ca03d2a4 Mon Sep 17 00:00:00 2001 From: alanna scott Date: Tue, 23 May 2017 12:22:50 -0700 Subject: [PATCH 4/7] fix long line --- superset/views/core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/superset/views/core.py b/superset/views/core.py index d3aa68ac7249a..e7152a81a5dd3 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -374,7 +374,8 @@ def pre_delete(self, obj): def add(self): datasources = ConnectorRegistry.get_all_datasources(db.session) datasources = [ - {'value': str(d.id) + '__' + d.type, 'label': repr(d)} for d in datasources + {'value': str(d.id) + '__' + d.type, 'label': repr(d)} + for d in datasources ] return self.render_template( "superset/add-slice.html", From d3745ed0ae3e9258e09f8192e95d50f419dfc3dd Mon Sep 17 00:00:00 2001 From: alanna scott Date: Wed, 7 Jun 2017 17:00:07 -0700 Subject: [PATCH 5/7] use underscore for template name --- superset/templates/superset/{add-slice.html => add_slice.html} | 0 superset/views/core.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename superset/templates/superset/{add-slice.html => add_slice.html} (100%) diff --git a/superset/templates/superset/add-slice.html b/superset/templates/superset/add_slice.html similarity index 100% rename from superset/templates/superset/add-slice.html rename to superset/templates/superset/add_slice.html diff --git a/superset/views/core.py b/superset/views/core.py index e7152a81a5dd3..109e6b685054b 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -378,7 +378,7 @@ def add(self): for d in datasources ] return self.render_template( - "superset/add-slice.html", + "superset/add_slice.html", bootstrap_data=json.dumps({ 'datasources': datasources, }), From 27e8a807895e87925308ac919805e32fc862aa70 Mon Sep 17 00:00:00 2001 From: alanna scott Date: Wed, 7 Jun 2017 17:18:25 -0700 Subject: [PATCH 6/7] fix controls path --- superset/assets/javascripts/addSlice/AddSliceContainer.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superset/assets/javascripts/addSlice/AddSliceContainer.jsx b/superset/assets/javascripts/addSlice/AddSliceContainer.jsx index d5855a55705aa..0f9ea27fad9a0 100644 --- a/superset/assets/javascripts/addSlice/AddSliceContainer.jsx +++ b/superset/assets/javascripts/addSlice/AddSliceContainer.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { Button, Panel, Grid, Row, Col } from 'react-bootstrap'; import Select from 'react-virtualized-select'; -import controls from '../explorev2/stores/controls'; +import controls from '../explore/stores/controls'; const propTypes = { datasources: PropTypes.arrayOf(PropTypes.shape({ From 97782d076fb1d800b35793cb717733aa68190fab Mon Sep 17 00:00:00 2001 From: alanna scott Date: Wed, 7 Jun 2017 17:39:02 -0700 Subject: [PATCH 7/7] fix vis types select --- .../assets/javascripts/addSlice/AddSliceContainer.jsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/superset/assets/javascripts/addSlice/AddSliceContainer.jsx b/superset/assets/javascripts/addSlice/AddSliceContainer.jsx index 0f9ea27fad9a0..2263e20e8d067 100644 --- a/superset/assets/javascripts/addSlice/AddSliceContainer.jsx +++ b/superset/assets/javascripts/addSlice/AddSliceContainer.jsx @@ -2,8 +2,7 @@ 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 controls from '../explore/stores/controls'; +import visTypes from '../explore/stores/visTypes'; const propTypes = { datasources: PropTypes.arrayOf(PropTypes.shape({ @@ -12,10 +11,11 @@ const propTypes = { })).isRequired, }; -class AddSliceContainer extends React.Component { +export default class AddSliceContainer extends React.PureComponent { constructor(props) { super(props); - this.vizTypeOptions = controls.viz_type.choices.map(vt => ({ label: vt[1], value: vt[0] })); + 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], @@ -95,5 +95,3 @@ class AddSliceContainer extends React.Component { } AddSliceContainer.propTypes = propTypes; - -export default AddSliceContainer;