-
Notifications
You must be signed in to change notification settings - Fork 13.8k
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
[Explore] Adding Adhoc Filters #4909
[Explore] Adding Adhoc Filters #4909
Conversation
fac4cf6
to
c85c7f1
Compare
Codecov Report
@@ Coverage Diff @@
## master #4909 +/- ##
==========================================
+ Coverage 77.07% 77.15% +0.07%
==========================================
Files 44 44
Lines 8558 8587 +29
==========================================
+ Hits 6596 6625 +29
Misses 1962 1962
Continue to review full report at Codecov.
|
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 looks good to me overall! 💯 a couple small comments :)
comparator: '10', | ||
clause: CLAUSES.WHERE, | ||
}); | ||
expect(adhocFilter.filterOptionName.length).to.be.above(10); |
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 because value > 10
has a length of 10? you could maybe make it more readable/obvious by doing 'value > 10'.length
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.
ah the optionNames are just randomized ids in this case. we use them to differentiate options when updating (sort of like a react key). so I can't predict what its value is.
|
||
duplicateWith(nextFields) { | ||
return new AdhocFilter({ | ||
...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.
not a blocker, but I think in general it's better for readability and bugginess to explicitly define which properties are copied (for ...this
, I think ...nextFields
is fine)
function translateToSql(adhocMetric, { useSimple } = {}) { | ||
if (adhocMetric.expressionType === EXPRESSION_TYPES.SIMPLE || useSimple) { | ||
const isMulti = MULTI_OPERATORS.indexOf(adhocMetric.operator) >= 0; | ||
return `${adhocMetric.subject} ${OPERATORS_TO_SQL[adhocMetric.operator]} ${isMulti ? '(\'' : ''}${isMulti ? adhocMetric.comparator.join("','") : adhocMetric.comparator}${isMulti ? '\')' : ''}`; |
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 break this up into different pieces to make it more readable.
onChange: PropTypes.func.isRequired, | ||
onClose: PropTypes.func.isRequired, | ||
onResize: PropTypes.func.isRequired, | ||
options: PropTypes.arrayOf(PropTypes.oneOfType([ |
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.
yay for well defined shapes! 🎉
width: startingWidth, | ||
height: startingHeight, | ||
}; | ||
document.addEventListener('mouseup', this.onMouseUp); |
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've typically seen this in the componentDidMount
lifecycle event but am not sure if it matters too much.
)), | ||
value: adhocFilter.operator, | ||
onChange: this.onOperatorChange, | ||
optionRenderer: VirtualizedRendererWrap(( |
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.
so are we just doing readable options for equals
? any reason not to make them all human readable?
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.
(to clarify, I think readable labels are good for the options dropdown, I think the mathematical representation >
, !=
etc is still probably better for the label since it needs to be concise.) not set on this though, I would imagine most people would understand either. an alternative I think we discussed would be to include both representations in the dropdown eg != (not equal to)
onExited={this.onOverlayExited} | ||
> | ||
<Label className="adhoc-filter-option"> | ||
<div onMouseDownCapture={(e) => { e.stopPropagation(); }}> |
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 define this as a class function so it's not initialized every render?
val: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]), | ||
}); | ||
|
||
const propTypes = { |
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.
typically you should define default props for anything not required because it's less likely you'll throw an error.
@@ -43,6 +44,7 @@ const defaultProps = { | |||
optionRenderer: opt => opt.label, | |||
valueRenderer: opt => opt.label, | |||
valueKey: 'value', | |||
noResultsText: 'No results found', |
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.
should this be a t()
?
superset/assets/src/explore/main.css
Outdated
} | ||
|
||
.adhoc-filter-edit-tabs > .nav-tabs > li > a { | ||
padding: 4px 4px 4px 4px; |
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 might know this but padding: 4px
also works
@@ -0,0 +1,5 @@ | |||
import visTypes from './explore/visTypes'; |
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.
Did you mean to commit 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.
oh geez
superset/viz.py
Outdated
'op': adhoc_filter.get('operator'), | ||
'val': adhoc_filter.get('comparator'), | ||
}) | ||
if expression_type == '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.
Nit. s/if/elif
.
superset/viz.py
Outdated
'op': adhoc_filter.get('operator'), | ||
'val': adhoc_filter.get('comparator'), | ||
}) | ||
if clause == 'HAVING': |
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. s/if/elif
.
superset/viz.py
Outdated
if expression_type == 'SQL': | ||
if clause == 'WHERE': | ||
sql_where_filters.append(adhoc_filter.get('sqlExpression')) | ||
if clause == 'HAVING': |
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. s/if/elif
.
superset/viz.py
Outdated
sql_having_filters.append(adhoc_filter.get('sqlExpression')) | ||
all_where_text = '' | ||
all_having_text = '' | ||
if len(sql_where_filters) == 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.
Why not simply,
all_where_text = ' AND '.join(['({})'.format(sql) for sql in sql_where_filters)])
Note list comprehensions are preferred over using map
, filter
etc.
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.
ah nice thats so much shorter 🎉
superset/viz.py
Outdated
all_where_text = ' AND '.join(list(map( | ||
lambda expression: '({})'.format(expression), sql_where_filters, | ||
))) | ||
if len(sql_having_filters) == 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.
See ^^.
d3c99d4
to
6e84b49
Compare
@williaster @michellethomas @john-bodley comments addressed, PTAL a when you have the chance! |
88e31b7
to
e3ec913
Compare
extras = { | ||
'where': ' AND '.join(['({})'.format(sql) for sql in sql_where_filters]), | ||
'having': ' AND '.join( | ||
['({})'.format(sql) for sql in sql_having_filters], |
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.
Does this go over the 90 character limit without wrapping?
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 one character 😢
🔥 🎆 🍾 🎊 |
* adding custom expressions to adhoc metrics * adjusted transitions and made the box expandable * adding adhoc filters * adjusted based on feedback
* adding custom expressions to adhoc metrics * adjusted transitions and made the box expandable * adding adhoc filters * adjusted based on feedback
@GabeLoins @john-bodley This is causing a bug with filters not being saved when visualizations are changed. specifically when switching to Table View: #5172 |
* adding custom expressions to adhoc metrics * adjusted transitions and made the box expandable * adding adhoc filters * adjusted based on feedback
Here I'm following a similar design pattern as Adhoc Metrics (#4663 and #4736). Adhoc Filters introduces a new control that unifies the 4 previous filter controls (free text where, free text having, structured where filter, structured having filter). It allows you to select a column or any saved metric / adhoc metric currently being used in the metric field to filter by:
and then once a metric or column is selected lets you switch between writing a SQL filter or simple filter for that metric/column:
(for druid datsources, metric and column filters can only be edited in the simple view. for table datsources, column filters can be edited in either but metrics filters can only be edited in the sql view)
This PR also removes the legacy filter controls. When loading a chart that has legacy filters already attached to it, they will be cast to adhoc filters:
Here are some more demos of the feature in action,
creating and editing filters:
transitioning between simple tab and sql tab:
selecting adhoc metrics to filter on:
notes to reviewers:
The meat of the frontend lies in the three AdhocFilterEditPopover* files and the AdhocFilterControl file. The meat of the backend lies in viz.py where I break adhoc filters out into the four base types of filters for processing.
reviewers:
@michellethomas @williaster @graceguo-supercat @john-bodley @mistercrunch