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

Added time filter to query search page #1329

Merged
merged 11 commits into from
Oct 28, 2016

Conversation

vera-liu
Copy link
Contributor

@vera-liu vera-liu commented Oct 12, 2016

Based on feedback from the meeting, added from-to time filter for query search

screen shot 2016-10-12 at 2 11 09 pm

@bkyryliuk
Copy link
Member

@vera-liu - could you also display query creation time in the search results ?

@vera-liu vera-liu force-pushed the vliu_time_filter branch 4 times, most recently from cdb54c0 to b044d71 Compare October 12, 2016 21:12
@vera-liu
Copy link
Contributor Author

@bkyryliuk Added

'running',
];

export const FROM_OPTIONS = [
Copy link
Member

Choose a reason for hiding this comment

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

could we reuse the options?

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:)

insertParams(baseUrl, params) {
return baseUrl + '?' + params.join('&');
getTimeFromSelection(selection) {
switch (selection) {
Copy link
Member

Choose a reason for hiding this comment

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

what about storing dictionary like:
{'1 hour ago': 1}
and then use: hoursAgo(dict['selection']) ?
now would be 0 hours ago and you could use 0 hours ago, days and years could be expressed in hours too

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 think year might be tricky here since it's not guaranteed to be a fixed number of hours?

Copy link
Member

Choose a reason for hiding this comment

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

what about storing the dict from option to the function that evaluates it ?

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 using the dict from value to function, but the function is evaluated when the dict is defined. There might be a way to delay the evaluation, but I figured switch statements might be a better option for mapping to functions?

Copy link
Member

Choose a reason for hiding this comment

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

function should take the attr, not to be evaluated.
@mistercrunch @ascott - what is your take here?

Copy link

Choose a reason for hiding this comment

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

i think switch/case works fine in this case. i think the function names could be made more clear. what is the advantage of using an object mapping over case/switch here @bkyryliuk ?

Copy link
Member

Choose a reason for hiding this comment

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

@ascott - less boilerplate.

getTimeFromSelection(selection) {
  return this.function_obj[selection]()
}

or smth like that

<div className="col-sm-1">
<Select
name="select-from"
placeholder="[From-]"
Copy link
Member

Choose a reason for hiding this comment

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

NIT, strings will have to be i18n eventually

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry I don't understand..could you elaborate more?

Copy link
Member

Choose a reason for hiding this comment

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

internationalization

Copy link
Contributor Author

Choose a reason for hiding this comment

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

could you give me an example?

Copy link

Choose a reason for hiding this comment

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

we don't have any internationalization support on the front end yet. we'll have to do a pass on the entire front end since there are a lot of instances that are not wrapped yet.

@@ -15,7 +15,7 @@ describe('QuerySearch', () => {

it('should have two Select', () => {
const wrapper = shallow(<QuerySearch />);
expect(wrapper.find(Select)).to.have.length(2);
expect(wrapper.find(Select)).to.have.length(4);
Copy link
Member

Choose a reason for hiding this comment

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

would be great to test date functions.
Do you have any plans write tests with state mutations ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hmm by state mutations do you mean tests for reducers? for now we haven't had much tests for functionalities, most are for rendering components in javascript

Copy link
Member

Choose a reason for hiding this comment

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

more like tests that test the logic rather than presence of the components.
it you select option expect that state is updated, etc
if you execute search - except url is like expected


if userId != 'null':
if user_id != 'null':
Copy link
Member

@bkyryliuk bkyryliuk Oct 13, 2016

Choose a reason for hiding this comment

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

s/if user_id != 'null':/ if user_id

same for others

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:)

userId = request.args.get('userId')
databaseId = request.args.get('databaseId')
searchText = request.args.get('searchText')
user_id = request.args.get('userId')
Copy link
Member

Choose a reason for hiding this comment

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

s/user_id/search_user_id - not to confuse with the logged in user

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

if from_time != 'null':
query = query.filter(models.Query.start_time > int(from_time))

if to_time != 'null':
Copy link
Member

Choose a reason for hiding this comment

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

add unit test that show that to and from works

Copy link
Contributor Author

@vera-liu vera-liu Oct 13, 2016

Choose a reason for hiding this comment

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

Done:)

databaseId = request.args.get('databaseId')
searchText = request.args.get('searchText')
user_id = request.args.get('userId')
database_id = request.args.get('databaseId')
Copy link
Member

Choose a reason for hiding this comment

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

s/.get('databaseId')/.get('database_id') - to be consistent
for now we use python conversion for args

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_time_filter branch 2 times, most recently from 979049f to e7c589c Compare October 14, 2016 19:16
@vera-liu
Copy link
Contributor Author

vera-liu commented Oct 14, 2016

Added more specs and tests as required. Ready for final review @bkyryliuk

`searchText=${this.state.searchText}`,
`status=${this.state.status}`,
this.state.userId ?
`user_id=${this.state.userId}` : '',
Copy link
Member

Choose a reason for hiding this comment

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

it should fit into the same line

status = request.args.get('status')
from_time = request.args.get('from')
Copy link
Member

Choose a reason for hiding this comment

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

please add a comment that time is the unix timestamp in seconds


sql_queries = query.limit(config.get("QUERY_SEARCH_LIMIT")).all()
Copy link
Member

Choose a reason for hiding this comment

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

QUERY_SEARCH_LIMIT may be not set or absent in the config. Please handle that case.

second_query = db.session.query(models.Query).filter_by(
sql='SELECT * FROM ab_permission').first()

from_time = 'from=' + \
Copy link
Member

Choose a reason for hiding this comment

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

it should fit into the same line.
Please use '{}'.format instead of + and str

resp = self.get_resp('/caravel/search_queries?'+'&'.join(params))
data = json.loads(resp)
self.assertEquals(1, len(data))
self.logout()
Copy link
Member

Choose a reason for hiding this comment

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

please test multiple use cases:
make sure that initially you have queries for 2 users, 1 user has queries for at least 2 databases

Test that:

  • time filter filters out queries both for to and from use cases
  • user_id filters given user queries
  • QUERY_SEARCH_LIMIT works
  • like query returns the expected queries

It would be nice to have all them as separate test case and you can you SetUp method to create initial dataset. Come by if you need some help with it.

import sinon from 'sinon';

describe('DatabaseSelect', () => {
it('should render', () => {
Copy link

@ascott ascott Oct 24, 2016

Choose a reason for hiding this comment

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

could break this up into 2 tests: it('is valid element') and it('renders'). it's a best practice to have an assertion for each test. so the render test should assert that some element rendered within DatabaseSelect

).to.equal(true);
});

it('should call onChange', () => {
Copy link

Choose a reason for hiding this comment

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

could be more explicit with this test name...it('calls onChange on select change')

expect(wrapper.find(Select)).to.have.length(4);
});

it('should update userId', () => {
Copy link

Choose a reason for hiding this comment

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

i generally think using should in tests is not neccessary. could make this more clear: it('updates userId on user select change')

@@ -5,4 +5,18 @@ export const STATE_BSSTYLE_MAP = {
success: 'success',
};

export const STATUS_OPTIONS = ['success', 'failed', 'running'];
export const STATUS_OPTIONS = [
Copy link

Choose a reason for hiding this comment

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

seems like this file should be called constants rather than common for clarity.

@@ -85,3 +85,24 @@ export const now = function () {
return moment().utc().valueOf();
};

export const hoursAgo = function (h) {
Copy link

Choose a reason for hiding this comment

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

could we use moment's Time from X methods here instead? http://momentjs.com/docs/#/displaying/from/

Copy link

Choose a reason for hiding this comment

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

i misunderstood that these functions are returning seconds from epoch to x hours ago, so the time from X methods don't make sense here. i think we could rename these for clarity...

resp = self.client.get('/caravel/search_queries?'+'&'.join(params))
self.assertEquals(200, resp.status_code)
self.login('admin')
self.run_sql("SELECT * FROM ab_user", 'admin', client_id='client_id_1')
Copy link
Member

@bkyryliuk bkyryliuk Oct 27, 2016

Choose a reason for hiding this comment

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

move query creation into the setUp function:
https://docs.python.org/2/library/unittest.html
also drop all queries before setting it up to avoid collision with other tests

make sre tearDown drops all queries too

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have run_sql() calls in a bunch of other test functions for different purposes, I'm afraid moving all of them to setUp() will result in rewriting all these functions

params = [from_time, to_time]
resp = self.get_resp('/caravel/search_queries?'+'&'.join(params))
data = json.loads(resp)
self.assertEquals(1, len(data))
Copy link
Member

Choose a reason for hiding this comment

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

assert that data has a query that falls into given period of time

# Test search queries on text
resp = self.get_resp('/caravel/search_queries?search_text=permission')
data = json.loads(resp)
self.assertEquals(1, len(data))
Copy link
Member

Choose a reason for hiding this comment

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

assert that result query text has permission in it

self.assertEquals(1, len(data))
resp = self.get_resp('/caravel/search_queries?database_id=2')
data = json.loads(resp)
self.assertEquals(0, len(data))
Copy link
Member

Choose a reason for hiding this comment

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

assert that there are no queries in the db that have db id = 2

# Test search queries on database Id
resp = self.get_resp('/caravel/search_queries?database_id=1')
data = json.loads(resp)
self.assertEquals(1, len(data))
Copy link
Member

Choose a reason for hiding this comment

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

assert that data has a query with db_id == 1

@vera-liu
Copy link
Contributor Author

@bkyryliuk I updated the python tests, will move sqllab tests out of core_tests in a separate PR


self.login('gamma')
self.run_sql("SELECT * FROM ab_permission", 'gamma', client_id='client_id_2')
self.logout()

def tearDown(self):
pass
Copy link
Member

Choose a reason for hiding this comment

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

would be nice to drop queries you've created in the tear down

data = json.loads(resp)
self.assertEquals(3, len(data))
for k, v in data.items():
assert data[k]['dbId'] == 1
Copy link
Member

@bkyryliuk bkyryliuk Oct 28, 2016

Choose a reason for hiding this comment

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

s/assert/self.assertEquals

better way would be

db_ids = [d[k]['dbId'] for k in data]
self.assertEquals([1, 1, 1], db_ids)

self.logout()

def test_search_query_on_user(self):
self.login('admin')
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 move admin login and logout to the setup and tear down.
specific logins can be handled in the test function

Copy link
Contributor Author

Choose a reason for hiding this comment

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

but we have access tests/other login logout in other tests, wouldn't they cause conflicts if we log in at Setup()?

resp = self.get_resp('/caravel/search_queries?user_id={}'.format(user.id))
data = json.loads(resp)
self.assertEquals(2, len(data))
for k, v in data.items():
Copy link
Member

Choose a reason for hiding this comment

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

do the same approach that I suggested for the dbIds

resp = self.get_resp('/caravel/search_queries?user_id={}'.format(user.id))
data = json.loads(resp)
self.assertEquals(1, len(data))
for (k, v) in data.items():
Copy link
Member

Choose a reason for hiding this comment

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

ditto, not need to loop through as there is just 1 query

resp = self.get_resp('/caravel/search_queries?status=success')
data = json.loads(resp)
self.assertEquals(2, len(data))
for k, v in data.items():
Copy link
Member

Choose a reason for hiding this comment

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

ditto

resp = self.get_resp('/caravel/search_queries?search_text=permission')
data = json.loads(resp)
self.assertEquals(1, len(data))
for k, v in data.items():
Copy link
Member

@bkyryliuk bkyryliuk Oct 28, 2016

Choose a reason for hiding this comment

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

by the way if you don't use the var you can name it _
for _, v in data.items()
better way would be I think to call data.iteritems or smth

data = json.loads(resp)
self.assertEquals(2, len(data))
for k, v in data.items():
assert v['startDttm'] > int(first_query_time) \
Copy link
Member

@bkyryliuk bkyryliuk Oct 28, 2016

Choose a reason for hiding this comment

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

use assertLess,
when data = 2, you can actually check each item manually

@vera-liu
Copy link
Contributor Author

all done @bkyryliuk

resp = self.get_resp('/caravel/search_queries?search_text=permission')
data = json.loads(resp)
self.assertEquals(1, len(data))
assert 'permission' in data.values()[0]['sql']
Copy link
Member

Choose a reason for hiding this comment

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

self.assertIn(a, b) :)

@bkyryliuk
Copy link
Member

Lovely, thanks for bearing with me !
LGTM once the last comment is addressed.

@vera-liu vera-liu force-pushed the vliu_time_filter branch 3 times, most recently from 208e30b to bc052cc Compare October 28, 2016 18:26
@vera-liu vera-liu merged commit 45efcb3 into apache:master Oct 28, 2016
@vera-liu vera-liu deleted the vliu_time_filter branch November 1, 2016 19:01
@mistercrunch mistercrunch added 🏷️ bot A label used by `supersetbot` to keep track of which PR where auto-tagged with release labels 🚢 0.13.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.13.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants