diff --git a/caravel/assets/javascripts/SqlLab/common.js b/caravel/assets/javascripts/SqlLab/common.js index 6d0ab5f5979df..4851d5dba6b61 100644 --- a/caravel/assets/javascripts/SqlLab/common.js +++ b/caravel/assets/javascripts/SqlLab/common.js @@ -4,5 +4,3 @@ export const STATE_BSSTYLE_MAP = { running: 'warning', success: 'success', }; - -export const STATUS_OPTIONS = ['success', 'failed', 'running']; diff --git a/caravel/assets/javascripts/SqlLab/components/App.jsx b/caravel/assets/javascripts/SqlLab/components/App.jsx deleted file mode 100644 index 9cf62e4559647..0000000000000 --- a/caravel/assets/javascripts/SqlLab/components/App.jsx +++ /dev/null @@ -1,71 +0,0 @@ -import * as Actions from '../actions'; -import React from 'react'; - -import TabbedSqlEditors from './TabbedSqlEditors'; -import QueryAutoRefresh from './QueryAutoRefresh'; -import QuerySearch from './QuerySearch'; -import Alerts from './Alerts'; - -import { bindActionCreators } from 'redux'; -import { connect } from 'react-redux'; - -class App extends React.Component { - constructor(props) { - super(props); - this.state = { - hash: window.location.hash, - }; - } - componentDidMount() { - window.addEventListener('hashchange', this.onHashChanged.bind(this)); - } - componentWillUnmount() { - window.removeEventListener('hashchange', this.onHashChanged.bind(this)); - } - onHashChanged() { - this.setState({ hash: window.location.hash }); - } - render() { - if (this.state.hash) { - return ( -
-
-
- -
-
-
- ); - } - return ( -
-
- - -
-
- -
-
-
-
- ); - } -} - -App.propTypes = { - alerts: React.PropTypes.array, -}; - -function mapStateToProps(state) { - return { - alerts: state.alerts, - }; -} -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators(Actions, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(App); diff --git a/caravel/assets/javascripts/SqlLab/components/DatabaseSelect.jsx b/caravel/assets/javascripts/SqlLab/components/DatabaseSelect.jsx deleted file mode 100644 index cac31609ee81a..0000000000000 --- a/caravel/assets/javascripts/SqlLab/components/DatabaseSelect.jsx +++ /dev/null @@ -1,51 +0,0 @@ -const $ = window.$ = require('jquery'); -import React from 'react'; -import Select from 'react-select'; - -class DatabaseSelect extends React.Component { - constructor(props) { - super(props); - this.state = { - databaseLoading: false, - databaseOptions: [], - databaseId: null, - }; - } - componentWillMount() { - this.fetchDatabaseOptions(); - } - changeDb(db) { - const val = (db) ? db.value : null; - this.setState({ databaseId: val }); - this.props.onChange(db); - } - fetchDatabaseOptions() { - this.setState({ databaseLoading: true }); - const url = '/databaseasync/api/read?_flt_0_expose_in_sqllab=1'; - $.get(url, (data) => { - const options = data.result.map((db) => ({ value: db.id, label: db.database_name })); - this.setState({ databaseOptions: options, databaseLoading: false }); - }); - } - render() { - return ( -
- -
-
- -
-
- +
+
+
+
+ Search Queries +
+
+
+ + ({ value: s, label: s }))} - value={this.state.status} - isLoading={false} - autosize={false} - onChange={this.changeStatus.bind(this)} - /> -
-
- +
+ +
+
); } } +QuerySearch.propTypes = { + queries: React.PropTypes.array, +}; +QuerySearch.defaultProps = { + queries: [], +}; +function mapStateToProps(state) { + return { + queries: state.queries, + }; +} +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators(Actions, dispatch), + }; +} -export default QuerySearch; +export default connect(mapStateToProps, mapDispatchToProps)(QuerySearch); diff --git a/caravel/assets/javascripts/SqlLab/components/QueryTable.jsx b/caravel/assets/javascripts/SqlLab/components/QueryTable.jsx index 0dbe38398e6ec..e9c35ea8c1cdb 100644 --- a/caravel/assets/javascripts/SqlLab/components/QueryTable.jsx +++ b/caravel/assets/javascripts/SqlLab/components/QueryTable.jsx @@ -123,7 +123,7 @@ QueryTable.propTypes = { queries: React.PropTypes.array, }; QueryTable.defaultProps = { - columns: ['started', 'duration', 'rows'], + columns: ['state', 'started', 'duration', 'progress', 'rows', 'sql', 'actions'], queries: [], }; diff --git a/caravel/assets/javascripts/SqlLab/components/SqlEditorLeft.jsx b/caravel/assets/javascripts/SqlLab/components/SqlEditorLeft.jsx index 25ca82d56cb59..25ea4fd723a02 100644 --- a/caravel/assets/javascripts/SqlLab/components/SqlEditorLeft.jsx +++ b/caravel/assets/javascripts/SqlLab/components/SqlEditorLeft.jsx @@ -8,13 +8,14 @@ import shortid from 'shortid'; import Select from 'react-select'; import { Label, Button } from 'react-bootstrap'; import TableElement from './TableElement'; -import DatabaseSelect from './DatabaseSelect'; class SqlEditorTopToolbar extends React.Component { constructor(props) { super(props); this.state = { + databaseLoading: false, + databaseOptions: [], schemaLoading: false, schemaOptions: [], tableLoading: false, @@ -22,20 +23,10 @@ class SqlEditorTopToolbar extends React.Component { }; } componentWillMount() { + this.fetchDatabaseOptions(); this.fetchSchemas(); this.fetchTables(); } - onChange(db) { - const val = (db) ? db.value : null; - this.setState({ schemaOptions: [] }); - this.props.actions.queryEditorSetDb(this.props.queryEditor, val); - if (!(db)) { - this.setState({ tableOptions: [] }); - } else { - this.fetchTables(val, this.props.queryEditor.schema); - this.fetchSchemas(val); - } - } resetState() { this.props.actions.resetState(); } @@ -73,6 +64,37 @@ class SqlEditorTopToolbar extends React.Component { }); } } + changeDb(db) { + const val = (db) ? db.value : null; + this.setState({ schemaOptions: [] }); + this.props.actions.queryEditorSetDb(this.props.queryEditor, val); + if (!(db)) { + this.setState({ tableOptions: [] }); + return; + } + this.fetchTables(val, this.props.queryEditor.schema); + this.fetchSchemas(val); + } + fetchDatabaseOptions() { + this.setState({ databaseLoading: true }); + const url = ( + '/databaseasync/api/read?' + + '_flt_0_expose_in_sqllab=1&' + + '_oc_DatabaseAsync=database_name&' + + '_od_DatabaseAsync=asc' + ); + $.get(url, (data) => { + const options = data.result.map((db) => ({ value: db.id, label: db.database_name })); + this.props.actions.setDatabases(data.result); + this.setState({ databaseOptions: options }); + this.setState({ databaseLoading: false }); + + // Auto select if only one option + if (options.length === 1) { + this.changeDb(options[0]); + } + }); + } closePopover(ref) { this.refs[ref].hide(); } @@ -114,7 +136,15 @@ class SqlEditorTopToolbar extends React.Component {
{networkAlert}
- + +
+ + +
+
+ +
+
+
+
+ ); +}; + +App.propTypes = { + alerts: React.PropTypes.array, +}; + +function mapStateToProps(state) { + return { + alerts: state.alerts, + }; +} +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators(Actions, dispatch), + }; +} + +const ReduxedApp = connect(mapStateToProps, mapDispatchToProps)(App); render( - + , document.getElementById('app') ); diff --git a/caravel/assets/javascripts/SqlLab/reducers.js b/caravel/assets/javascripts/SqlLab/reducers.js index d711d64525c4e..b7a850c982ae8 100644 --- a/caravel/assets/javascripts/SqlLab/reducers.js +++ b/caravel/assets/javascripts/SqlLab/reducers.js @@ -13,7 +13,6 @@ const defaultQueryEditor = { dbId: null, }; -// TODO(bkyryliuk): document the object schemas export const initialState = { alerts: [], networkOn: true, diff --git a/caravel/assets/spec/javascripts/sqllab/QuerySearch_spec.jsx b/caravel/assets/spec/javascripts/sqllab/QuerySearch_spec.jsx deleted file mode 100644 index 076771129aaa7..0000000000000 --- a/caravel/assets/spec/javascripts/sqllab/QuerySearch_spec.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import Select from 'react-select'; -import { Button } from 'react-bootstrap'; -import QuerySearch from '../../../javascripts/SqlLab/components/QuerySearch'; -import { shallow } from 'enzyme'; -import { describe, it } from 'mocha'; -import { expect } from 'chai'; - -describe('QuerySearch', () => { - it('should render', () => { - expect( - React.isValidElement() - ).to.equal(true); - }); - - it('should have two Select', () => { - const wrapper = shallow(); - expect(wrapper.find(Select)).to.have.length(2); - }); - - it('should have one input for searchText', () => { - const wrapper = shallow(); - expect(wrapper.find('input')).to.have.length(1); - }); - - it('should have one Button', () => { - const wrapper = shallow(); - expect(wrapper.find(Button)).to.have.length(1); - }); -}); diff --git a/caravel/config.py b/caravel/config.py index 267a7777333a9..ca378d8cd6187 100644 --- a/caravel/config.py +++ b/caravel/config.py @@ -47,9 +47,6 @@ # SQLALCHEMY_DATABASE_URI = 'mysql://myapp@localhost/myapp' # SQLALCHEMY_DATABASE_URI = 'postgresql://root:password@localhost/myapp' -# The limit of queries fetched for query search -QUERY_SEARCH_LIMIT = 1000 - # Flask-WTF flag for CSRF CSRF_ENABLED = True diff --git a/caravel/utils.py b/caravel/utils.py index 670c0694e02b3..f646a4342365e 100644 --- a/caravel/utils.py +++ b/caravel/utils.py @@ -217,7 +217,7 @@ def init(caravel): 'RoleModelView', 'Security', 'UserDBModelView', - 'SQL Lab', + 'SQL Lab alpha', 'AccessRequestsModelView', ]) diff --git a/caravel/views.py b/caravel/views.py index 415a03571cf06..78d424523f490 100755 --- a/caravel/views.py +++ b/caravel/views.py @@ -1,3 +1,4 @@ +"""Flask web views for Caravel""" from __future__ import absolute_import from __future__ import division from __future__ import print_function @@ -1863,41 +1864,6 @@ def queries(self, last_updated_ms): status=200, mimetype="application/json") - @has_access - @expose("/search_queries") - @log_this - def search_queries(self): - """Search for queries.""" - query = db.session.query(models.Query) - userId = request.args.get('userId') - databaseId = request.args.get('databaseId') - searchText = request.args.get('searchText') - status = request.args.get('status') - - if userId != 'null': - # Filter on db Id - query = query.filter(models.Query.user_id == userId) - - if databaseId != 'null': - # Filter on db Id - query = query.filter(models.Query.database_id == databaseId) - - if status != 'null': - # Filter on status - query = query.filter(models.Query.status == status) - - if searchText != 'null': - # Filter on search text - query = query.filter(models.Query.sql.like('%{}%'.format(searchText))) - - sql_queries = query.limit(config.get("QUERY_SEARCH_LIMIT")).all() - - dict_queries = {q.client_id: q.to_dict() for q in sql_queries} - return Response( - json.dumps(dict_queries, default=utils.json_int_dttm_ser), - status=200, - mimetype="application/json") - @has_access @expose("/refresh_datasources/") def refresh_datasources(self): @@ -1972,15 +1938,9 @@ class CssTemplateModelView(CaravelModelView, DeleteMixin): category_icon='') appbuilder.add_link( - 'SQL Editor', + 'SQL Lab alpha', href='/caravel/sqllab', - icon="fa-flask", - category='SQL Lab') -appbuilder.add_link( - 'Query Search', - href='/caravel/sqllab#search', - icon="fa-flask", - category='SQL Lab') + icon="fa-flask") @app.after_request diff --git a/tests/core_tests.py b/tests/core_tests.py index af90591e31f7c..bb1ace23a8cd1 100644 --- a/tests/core_tests.py +++ b/tests/core_tests.py @@ -69,7 +69,7 @@ def assert_admin_view_menus_in(role_name, assert_func): assert_func('RoleModelView', view_menus) assert_func('Security', view_menus) assert_func('UserDBModelView', view_menus) - assert_func('SQL Lab', + assert_func('SQL Lab alpha', view_menus) assert_func('AccessRequestsModelView', view_menus) @@ -670,15 +670,6 @@ def test_queries_endpoint(self): resp = self.client.get('/caravel/queries/{}'.format(0)) self.assertEquals(403, resp.status_code) - def test_search_query_endpoint(self): - userId = 'userId=null' - databaseId = 'databaseId=null' - searchText = 'searchText=null' - status = 'status=success' - params = [userId, databaseId, searchText, status] - resp = self.client.get('/caravel/search_queries?'+'&'.join(params)) - self.assertEquals(200, resp.status_code) - def test_public_user_dashboard_access(self): # Try access before adding appropriate permissions. self.revoke_public_access('birth_names')