-
Notifications
You must be signed in to change notification settings - Fork 14.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Query Search Page #1122
Query Search Page #1122
Conversation
815f9d4
to
132dd08
Compare
132dd08
to
c532b49
Compare
@@ -17,6 +17,8 @@ export const QUERY_EDITOR_SET_TITLE = 'QUERY_EDITOR_SET_TITLE'; | |||
export const QUERY_EDITOR_SET_AUTORUN = 'QUERY_EDITOR_SET_AUTORUN'; | |||
export const QUERY_EDITOR_SET_SQL = 'QUERY_EDITOR_SET_SQL'; | |||
export const SET_DATABASES = 'SET_DATABASES'; | |||
export const SET_FILTER_DATABASES = 'SET_FILTER_DATABASES'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prefix them QUERY_SEARCH
componentWillReceiveProps(nextProps) { | ||
this.refreshQueries(nextProps); | ||
} | ||
getQueryState(val) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getQueryState(val) {
switch (val) {
case 1:
return 'success';
etc will be a bit less of code
refreshQueries(nextProps) { | ||
const userId = nextProps.queryFilter.userId; | ||
const dbId = nextProps.queryFilter.queryDbId; | ||
let sql = nextProps.queryFilter.searchText; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/sql/searchText ?
const q = data[id]; | ||
newQueriesArray.push(q); | ||
} | ||
this.setState({ queriesArray: newQueriesArray }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you just do this.setState({ queriesArray: data }); ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
data is an object, I had to transfer it to an array for QueryTable, not sure if there's a better option
constructor(props) { | ||
super(props); | ||
this.state = { | ||
databaseLoading: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could just check if the list of the databases is empty or not rather than keeping it in separate var.
mimetype="application/json") | ||
|
||
@has_access | ||
@expose("/search_queries/userId=<userId>" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe s/search_queries/sql_lab/search
|
||
@has_access | ||
@expose("/search_queries/userId=<userId>" | ||
"&dbId=<dbId>&sql=<search_text>&state=<state>") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you may want to make want to make the request post or somehow handle special characters
"""Get all the queries.""" | ||
if not g.user.get_id(): | ||
return Response( | ||
json.dumps({'error': "Please login to access the queries."}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update error message
search_str = '%{}%'.format(search_text) | ||
|
||
query = db.session.query(models.Query) | ||
if not userId == 'null': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe sqlalchemy is fine with the None values, it just ignores them, your query could look like:
query = query.filter(
models.Query.user_id == userId,
models.Query.database_id == dbId, ...
@mistercrunch - could you confirm that?
search_str = '%{}%'.format(search_text) | ||
|
||
query = db.session.query(models.Query) | ||
if not userId == 'null': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
userId = None if userId == 'null' else userId
if not search_text == 'null': | ||
query = query.filter(models.Query.sql.like(search_str)) | ||
|
||
sql_queries = query.limit(1000).all() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make it a configurable param in the config.py
|
||
appbuilder.add_link('My Queries', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/My Queries/SQL Editor ?
} | ||
getQueryState(val) { | ||
let status; | ||
switch (val) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need to map strings to arbitrary ints, let's just use the strings directly
@log_this | ||
def search_queries(self, userId, dbId, search_text, state): | ||
"""Get all the queries.""" | ||
if not g.user.get_id(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@has_access should take care of that
# Filter on user Id | ||
query = query.filter(models.Query.user_id == userId) | ||
|
||
if not dbId == 'null': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's oddly twisted, why no if dbId != 'null':
if not (not (not True)):
@@ -0,0 +1,76 @@ | |||
import React from 'react'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This component is largely similar (if not directly copied) from the existing QueryTable
component. Why not just use that component? It receives an array of queries and a list of columns to expose which should work perfectly.
@@ -27,9 +28,20 @@ if (process.env.NODE_ENV === 'dev') { | |||
let store = createStore(sqlLabReducer, initialState, enhancer); | |||
|
|||
// jquery hack to highlight the navbar menu | |||
$('a[href="/caravel/sqllab"]').parent().addClass('active'); | |||
// $('a[href="/caravel/sqllab"]').parent().addClass('active'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this not needed anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This always highlights the 'SQL Lab/My Queries' in the dropdown menu when 'Query Search' is selected. Not sure how to work around this
let sql = nextProps.queryFilter.searchText; | ||
if (sql === '') sql = 'null'; | ||
const showState = this.getQueryState(nextProps.queryFilter.queryState); | ||
const url = ` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this a line break after the backtick? my brain interpreter doesn't understand how this work...
const options = data.result.map((db) => ({ value: db.id, label: db.database_name })); | ||
this.props.actions.setFilterDatabases(data.result); | ||
this.setState({ databaseOptions: options }); | ||
this.setState({ databaseLoading: false }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can call setState only once here instead of twice
if (status === 'success') { | ||
const options = data.map((user) => ({ value: user[0], label: user[1] })); | ||
this.setState({ userLoading: false }); | ||
this.setState({ userOptions: options }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
single call to setState
this.setState({ userLoading: false }); | ||
this.setState({ userOptions: options }); | ||
} else { | ||
console.log('get users failed'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not useful
const val = (status) ? status.value : null; | ||
this.setState({ queryState: val }); | ||
} | ||
fetchDatabaseOptions() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is copy pasta from SqlEditorLeft
, we should refactor a new component DatabaseSelect out and use it in both places
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once we get DatabaseSelect as a child component, the select value would have to be passed from it back to QuerySearch or QueryEditor. Do we want to pass the value from child to parent?
href='/caravel/sqllab', icon="fa-flask", category='SQL Lab') | ||
appbuilder.add_link('Query Search', | ||
|
||
href='/caravel/sqllab?search', icon="fa-flask", category='SQL Lab') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to use hashtag here, but if I use sqllab#search,to switch from SQL Editor to Query Search, the user has to press 'refresh', not sure if there's a better appraoch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you have to bind an even to hash change:
https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onhashchange
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done :)
148f798
to
839733a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few minor comments, but overall looks like this is almost done!
} | ||
|
||
DatabaseSelect.propTypes = { | ||
onDatabaseSelected: React.PropTypes.func, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd this with the standard name: onChange
for (let i = 0; i < params.length; i++) { | ||
if (i === params.length - 1) url += params[i]; | ||
else url += params[i] + '&'; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this whole function can be replaced by:
return baseUrl + '?' + params.join('&');
const newQueriesArray = []; | ||
for (const id in data) { | ||
const q = data[id]; | ||
newQueriesArray.push(q); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one line instead of 2:
newQueriesArray.push(data[id]);
</div> | ||
<div className="col-sm-2"> | ||
<DatabaseSelect onDatabaseSelected={this.onDatabaseSelected.bind(this)} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
related to comment above: onChange={this.onDatabaseSelected.bind(this)
@@ -122,7 +122,7 @@ QueryTable.propTypes = { | |||
queries: React.PropTypes.array, | |||
}; | |||
QueryTable.defaultProps = { | |||
columns: ['state', 'started', 'duration', 'progress', 'rows', 'sql', 'actions'], | |||
columns: ['state', 'progress', 'sql'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not these fields: started, duration & rows?
autosize={false} | ||
onChange={this.changeDb.bind(this)} | ||
/> | ||
<DatabaseSelect onDatabaseSelected={this.onDatabaseSelected.bind(this)} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onChange
if (!(db)) { | ||
this.setState({ tableOptions: [] }); | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could this be an else statement, rather than an early return?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!db) {
// set state
} else {
// do other things
}
fetchDatabaseOptions() { | ||
this.setState({ databaseLoading: true }); | ||
const url = '/databaseasync/api/read?_flt_0_expose_in_sqllab=1'; | ||
$.get(url, (data) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think we should use a smaller lib for ajax/get/post requests. there is a polyfill for the window.fetch standard.
https://www.npmjs.com/package/whatwg-fetch
it's only ~3kb gzipped compared to ~70kb to include jquery on the page.
fetchDatabaseOptions() { | ||
this.setState({ databaseLoading: true }); | ||
const url = '/databaseasync/api/read?_flt_0_expose_in_sqllab=1'; | ||
$.get(url, (data) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not a blocker for this PR. i can create a new issue to go through and change how we do this across the app.
a93d374
to
235d0a9
Compare
Added specs and endpoint test, made more modifications |
LGTM |
235d0a9
to
85cbd83
Compare
fc120a5
to
821f676
Compare
Query search page under SQL Lab in the menu header
What's done:
Todo: