);
}
}
-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 connect(mapStateToProps, mapDispatchToProps)(QuerySearch);
+export default QuerySearch;
diff --git a/caravel/assets/javascripts/SqlLab/components/QueryTable.jsx b/caravel/assets/javascripts/SqlLab/components/QueryTable.jsx
index e9c35ea8c1cdb..0dbe38398e6ec 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: ['state', 'started', 'duration', 'progress', 'rows', 'sql', 'actions'],
+ columns: ['started', 'duration', 'rows'],
queries: [],
};
diff --git a/caravel/assets/javascripts/SqlLab/components/SqlEditorLeft.jsx b/caravel/assets/javascripts/SqlLab/components/SqlEditorLeft.jsx
index 25ea4fd723a02..25ca82d56cb59 100644
--- a/caravel/assets/javascripts/SqlLab/components/SqlEditorLeft.jsx
+++ b/caravel/assets/javascripts/SqlLab/components/SqlEditorLeft.jsx
@@ -8,14 +8,13 @@ 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,
@@ -23,10 +22,20 @@ 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();
}
@@ -64,37 +73,6 @@ 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();
}
@@ -136,15 +114,7 @@ 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);
+$('a:contains("SQL Lab")').parent().addClass('active');
render(
-
+
,
document.getElementById('app')
);
diff --git a/caravel/assets/javascripts/SqlLab/reducers.js b/caravel/assets/javascripts/SqlLab/reducers.js
index b7a850c982ae8..d711d64525c4e 100644
--- a/caravel/assets/javascripts/SqlLab/reducers.js
+++ b/caravel/assets/javascripts/SqlLab/reducers.js
@@ -13,6 +13,7 @@ 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
new file mode 100644
index 0000000000000..076771129aaa7
--- /dev/null
+++ b/caravel/assets/spec/javascripts/sqllab/QuerySearch_spec.jsx
@@ -0,0 +1,30 @@
+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 ca378d8cd6187..267a7777333a9 100644
--- a/caravel/config.py
+++ b/caravel/config.py
@@ -47,6 +47,9 @@
# 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 f646a4342365e..670c0694e02b3 100644
--- a/caravel/utils.py
+++ b/caravel/utils.py
@@ -217,7 +217,7 @@ def init(caravel):
'RoleModelView',
'Security',
'UserDBModelView',
- 'SQL Lab
alpha',
+ 'SQL Lab',
'AccessRequestsModelView',
])
diff --git a/caravel/views.py b/caravel/views.py
index 78d424523f490..415a03571cf06 100755
--- a/caravel/views.py
+++ b/caravel/views.py
@@ -1,4 +1,3 @@
-"""Flask web views for Caravel"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
@@ -1864,6 +1863,41 @@ 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):
@@ -1938,9 +1972,15 @@ class CssTemplateModelView(CaravelModelView, DeleteMixin):
category_icon='')
appbuilder.add_link(
- 'SQL Lab
alpha',
+ 'SQL Editor',
href='/caravel/sqllab',
- icon="fa-flask")
+ icon="fa-flask",
+ category='SQL Lab')
+appbuilder.add_link(
+ 'Query Search',
+ href='/caravel/sqllab#search',
+ icon="fa-flask",
+ category='SQL Lab')
@app.after_request
diff --git a/tests/core_tests.py b/tests/core_tests.py
index bb1ace23a8cd1..af90591e31f7c 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
alpha',
+ assert_func('SQL Lab',
view_menus)
assert_func('AccessRequestsModelView', view_menus)
@@ -670,6 +670,15 @@ 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')