Skip to content
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

Merged
merged 7 commits into from
Sep 23, 2016
Merged

Query Search Page #1122

merged 7 commits into from
Sep 23, 2016

Conversation

vera-liu
Copy link
Contributor

@vera-liu vera-liu commented Sep 16, 2016

Query search page under SQL Lab in the menu header

What's done:

  • Endpoint for getting all users
  • Endpoint for retrieving all queries with a databaseId/userId/status/string for filtering on sql
  • Query Search page with four fields
    screen shot 2016-09-15 at 5 12 08 pm
  • Made modifications based on comments, ready for second round review: @mistercrunch
    Todo:
  • Add visualize modal triggered in action bar to QuerySearchTable as that in QueryHistory
  • make userId and dbId in Query result clickable links to set a specific userId or dbId in filter
  • Make a copy-url function beside each entry of query result, once generated the url will lead user to a specific query
  • Time range for searching queries
  • Alerts or Loading icon while queries are being fetched

@@ -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';
Copy link
Member

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) {
Copy link
Member

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;
Copy link
Member

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 });
Copy link
Member

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 }); ?

Copy link
Contributor Author

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,
Copy link
Member

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>"
Copy link
Member

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>")
Copy link
Member

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."}),
Copy link
Member

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':
Copy link
Member

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':
Copy link
Member

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()
Copy link
Member

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',
Copy link
Member

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) {
Copy link
Member

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():
Copy link
Member

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':
Copy link
Member

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';
Copy link
Member

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');
Copy link
Member

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?

Copy link
Contributor Author

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 = `
Copy link
Member

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 });
Copy link
Member

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 });
Copy link
Member

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');
Copy link
Member

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() {
Copy link
Member

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

Copy link
Contributor Author

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')
Copy link
Contributor Author

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done :)

@vera-liu vera-liu force-pushed the vliu_query_search branch 4 times, most recently from 148f798 to 839733a Compare September 19, 2016 20:49
Copy link
Member

@mistercrunch mistercrunch left a 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,
Copy link
Member

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] + '&';
}
Copy link
Member

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);
Copy link
Member

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)} />
Copy link
Member

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'],
Copy link
Member

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)} />
Copy link
Member

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;
}
Copy link
Contributor

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?

Copy link
Contributor

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) => {
Copy link
Contributor

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) => {
Copy link
Contributor

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.

@vera-liu
Copy link
Contributor Author

vera-liu commented Sep 20, 2016

Added specs and endpoint test, made more modifications
@mistercrunch @ascott @bkyryliuk

@mistercrunch
Copy link
Member

LGTM

@vera-liu vera-liu merged commit d5c5c0d into apache:master Sep 23, 2016
zhaoyongjie added a commit to zhaoyongjie/incubator-superset that referenced this pull request Nov 17, 2021
zhaoyongjie added a commit to zhaoyongjie/incubator-superset that referenced this pull request Nov 24, 2021
zhaoyongjie added a commit to zhaoyongjie/incubator-superset that referenced this pull request Nov 25, 2021
zhaoyongjie added a commit to zhaoyongjie/incubator-superset that referenced this pull request Nov 26, 2021
@mistercrunch mistercrunch added 🏷️ bot A label used by `supersetbot` to keep track of which PR where auto-tagged with release labels 🚢 0.11.0 labels Feb 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🏷️ bot A label used by `supersetbot` to keep track of which PR where auto-tagged with release labels 🚢 0.11.0
Projects
No open projects
sql lab
in progress
Development

Successfully merging this pull request may close these issues.

None yet

4 participants