-
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
Added time filter to query search page #1329
Conversation
f4c9837
to
912ef21
Compare
@vera-liu - could you also display query creation time in the search results ? |
cdb54c0
to
b044d71
Compare
@bkyryliuk Added |
1265b43
to
6fdba82
Compare
'running', | ||
]; | ||
|
||
export const FROM_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.
could we reuse the 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.
Done:)
insertParams(baseUrl, params) { | ||
return baseUrl + '?' + params.join('&'); | ||
getTimeFromSelection(selection) { | ||
switch (selection) { |
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.
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
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 year might be tricky here since it's not guaranteed to be a fixed number of hours?
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.
what about storing the dict from option to the function that evaluates it ?
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 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?
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.
function should take the attr, not to be evaluated.
@mistercrunch @ascott - what is your take here?
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 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 ?
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.
@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-]" |
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.
NIT, strings will have to be i18n eventually
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.
Sorry I don't understand..could you elaborate more?
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.
internationalization
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 give me an example?
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.
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); |
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.
would be great to test date functions.
Do you have any plans write tests with state mutations ?
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.
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
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.
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': |
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/if user_id != 'null':/ if user_id
same for others
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:)
userId = request.args.get('userId') | ||
databaseId = request.args.get('databaseId') | ||
searchText = request.args.get('searchText') | ||
user_id = request.args.get('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.
s/user_id/search_user_id - not to confuse with the logged in user
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;)
if from_time != 'null': | ||
query = query.filter(models.Query.start_time > int(from_time)) | ||
|
||
if to_time != '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.
add unit test that show that to and from works
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:)
databaseId = request.args.get('databaseId') | ||
searchText = request.args.get('searchText') | ||
user_id = request.args.get('userId') | ||
database_id = request.args.get('databaseId') |
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/.get('databaseId')/.get('database_id') - to be consistent
for now we use python conversion for args
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;)
979049f
to
e7c589c
Compare
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}` : '', |
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.
it should fit into the same line
status = request.args.get('status') | ||
from_time = request.args.get('from') |
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.
please add a comment that time is the unix timestamp in seconds
|
||
sql_queries = query.limit(config.get("QUERY_SEARCH_LIMIT")).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.
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=' + \ |
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.
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() |
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.
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
andfrom
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', () => { |
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 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', () => { |
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 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', () => { |
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 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 = [ |
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.
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) { |
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 we use moment's Time from X methods here instead? http://momentjs.com/docs/#/displaying/from/
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 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') |
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.
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
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.
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)) |
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.
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)) |
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.
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)) |
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.
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)) |
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.
assert that data has a query with db_id == 1
@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 |
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.
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 |
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/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') |
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 move admin login and logout to the setup and tear down.
specific logins can be handled in the test function
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.
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(): |
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.
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(): |
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.
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(): |
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.
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(): |
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.
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) \ |
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.
use assertLess,
when data = 2, you can actually check each item manually
eb65881
to
8ae7b16
Compare
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'] |
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.
self.assertIn(a, b) :)
Lovely, thanks for bearing with me ! |
208e30b
to
bc052cc
Compare
3fe945f
to
fa6de67
Compare
Based on feedback from the meeting, added from-to time filter for query search