-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
Click to filter values in Discover doc table #5344
Conversation
Jenkins standing by to test this. If you aren't a maintainer, you can ignore this comment. Someone with commit access, please review this and clear it for Jenkins to run; then say 'jenkins, test it'. |
@rashidkpc Could this be tagged for review please? |
I think we're still back and forth on how we want this designed. I don't love the look, though I haven't given a lot of thought into how to do it differently. We have a lot of stuff in the review queue for 4.4.0 right now, I don't want to try to jam this into that release. |
@rashidkpc Have you had any more thoughts on this? I spend a lot of my day opening up documents just to filter them, so this would be really handy. |
Jenkins standing by to test this. If you aren't a maintainer, you can ignore this comment. Someone with commit access, please review this and clear it for Jenkins to run; then say 'jenkins, test it'. |
jenkins, test it |
This is pretty out of date, tests are failing, interested in getting this updated @jimmyjones2? |
Hey @spalger really keen to get this in, but do you and @rashidkpc want it done differently before you'll merge? |
What about making the whole value clickable with a modifier key or something? @tbragin other ideas? @simianhacker? |
It seems like it would be cool instead of injecting the buttons into the table field we added them to some kind of tooltip. That way it doesn't cause a reflow when the field is too narrow. |
/me whispers "right-click" |
I agree the current implementation has reflow problems - sometimes the two icons appear on the same row, other times they appear on different rows, and yet another time they appear in different places depending on where you moused over (see first video). In addition, while I like the discoverability of something available on mouseover and the use of the standard icons, I don't think the behavior is consistent with how table filtering is done in other contexts in Kibana. Since this behavior applies to both Discover tab as well as the raw results table on the Dashboard tab, we have to consider how filtering is done in a the aggregated Data Table as well.
See second video. Would it make sense to have the same type of interaction for both tables? Regarding other suggestions:
|
I agree that table filtering should be the same in all contexts. I'm not a big fan of the Data Table:
How about something similar for both where the +/- icons floated over the end of the text if it would otherwise wrap or cause the column to grow in width? |
@tbragin I was thinking of something like below, which doesn't increase the width of columns: |
Honestly, I still prefer the current approach of the Data Table, but it may make sense to get another pair of eyes on this. Perhaps @alt74 has an opinion one way or another? Whatever solution we arrive at here, I'd advocate for keeping the behavior consistent between filtering the Data Table with aggregated results and the "raw results" table, either on Discover or in the context of Dashboard. |
@jimmyjones2 Sorry that this has stagnated for so long. Do you still want to get this over the line? |
Can one of the admins verify this patch? |
@epixa Yup, this is really useful for me in $DAYJOB. I've implemented what's in the screenshot on 1 May but not pushed it as there didn't seems to be any consensus... |
I quite like the idea of floating the controls over the right side of the column, as posted on May 1st. It feels like a very familiar interaction that's used in a lot of other apps (see Google Inbox as one prominent example) and it gives us some room to add more buttons if we need them. What do you think @cjcenizal ? It looks like we need to make a decision on design before we can move forward with this PR. |
@cjcenizal @Bargs It doesn't bother you that we'd have floating controls for filtering on this table and a different interaction for the data table? To me, this feels like it would be confusing for the user. |
I'd be in favor of adding the floating controls to the data table as well. There's another request for something just like that here #2184 |
@tbragin @Bargs The May 1 screenshot looks pretty cool to me. @jimmyjones2 Can you update the PR with the latest (and maybe rebase master) so I can try it out? I'd really like to try out the UX and see if we can get this in ASAP. |
@cjcenizal Rebased on master. Let me know what you think! |
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.
@jimmyjones2 I think this is sweet! I played around with it a bit and I'm sure we can find some ways to improve the experience, but overall it's excellent. I think this only needs a couple tweaks and we should be OK to merge.
@Bargs @tbragin I think we can migrate these changes to the data table in another PR. Looking forward, I could see us surfacing these controls in a tooltip or popover but for now I think this is fine. Either way, I think surfacing this functionality in the context of the cell is really useful.
const cellTemplate = _.template(noWhiteSpace(require('ui/doc_table/components/table_row/cell.html'))); | ||
const truncateByHeightTemplate = _.template(noWhiteSpace(require('ui/partials/truncate_by_height.html'))); | ||
const filterManager = Private(require('ui/filter_manager')); |
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 change this require
call to an import
and move it to the top of the file?
import FilterManagerProvider from 'ui/filter_manager';
// and inside the directive...
const filterManager = Private(FilterManagerProvider);
@@ -83,6 +84,14 @@ module.directive('kbnTableRow', function ($compile) { | |||
createSummaryRow($scope.row, $scope.row._id); | |||
}); | |||
|
|||
$scope.inlineFilter = function ($event, type) { |
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.
Can we make this function named so it's easier to read in a call stack?
$scope.inlineFilter = function inlineFilter($event, type) {
})); | ||
} | ||
|
||
$scope.columns.forEach(function (column) { | ||
var filterable = mapping[column] ? mapping[column].filterable : false; | ||
const flattened = indexPattern.flattenHit(row); | ||
filterable = flattened[column] === undefined ? false : filterable; |
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.
Normally I try to avoid ternaries but in this case I think a ternary actually makes the code a little more readable. It also lets us avoid mutating the filterable
variable:
const flattenedRow = indexPattern.flattenHit(row);
const isFilterable =
flattenedRow[column] === undefined
? false
: !mapping[column]
? false
: mapping[column].filterable;
@@ -10,4 +10,11 @@ | |||
%> | |||
<td <%= attributes %>> | |||
<%= formatted %> | |||
<span class="table-cell-filter"> | |||
<% if (filterable) { %> | |||
<i> </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.
What does this element do? I tried to removing it and it didn't seem to have an effect.
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.
Oops, was from the old version of the patch!
<% if (filterable) { %> | ||
<i> </i> | ||
<i ng-click="inlineFilter($event, '+')" class="fa fa-search-plus" data-column="<%- column %>" tooltip="Filter for value" tooltip-append-to-body="1"></i> | ||
<i ng-click="inlineFilter($event, '-')" class="fa fa-search-minus" data-column="<%- column %>" tooltip="Filter out value" tooltip-append-to-body="1"></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.
Can we make a few changes here:
- Instead of
<i>
elements, can we use<span>
elements? (They're more semantic) - Let's format this markup according to our HTML styleguide.
- Let's add additional classes to the icons to tweak their styles.
The resulting markup would look like this:
<span
ng-click="inlineFilter($event, '+')"
class="fa fa-search-plus docTableRowFilterIcon"
data-column="<%- column %>"
tooltip="Filter for value"
tooltip-append-to-body="1"
></span>
<span
ng-click="inlineFilter($event, '-')"
class="fa fa-search-minus docTableRowFilterIcon"
ata-column="<%- column %>"
tooltip="Filter out value"
tooltip-append-to-body="1"
></span>
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.
BTW I just edited my markup example above... I totally forgot to implement #1 in my list 😄
.table-cell-filter { | ||
visibility: visible; | ||
} | ||
} |
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 move these styles to ui/public/doc_table/doc_table.less
and nest everything inside of the discover-table-row
class? This should scope everything to just the Discover table.
.discover-table-row {
td {
position: relative;
.table-cell-filter {
position: absolute;
background-color: rgba(255, 255, 255, 0.75);
white-space:nowrap;
right: 0;
visibility: hidden;
}
&:hover {
.table-cell-filter {
visibility: visible;
}
}
}
}
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.
@@ -24,9 +24,10 @@ const MIN_LINE_LENGTH = 20; | |||
* <tr ng-repeat="row in rows" kbn-table-row="row"></tr> | |||
* ``` | |||
*/ | |||
module.directive('kbnTableRow', function ($compile) { | |||
module.directive('kbnTableRow', ['$compile', 'Private', function ($compile, Private) { |
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.
Thanks for making this improvement! :D
@cjcenizal Done, thanks for the help/feedback! |
& + & { | ||
margin-left: 5px; | ||
} | ||
} |
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 minor tweak: could you un-nest this selector entirely so that this file looks like this:
doc-table {
// styles...
}
/**
* 1. Align icon with text in cell.
*/
.docTableRowFilterIcon {
font-size: 14px;
line-height: 1; /* 1 */
& + & {
margin-left: 5px;
}
}
This removes unnecessary specificity and fixes a bug caused by the & + &
selector.
@cjcenizal Done, thanks again for your help |
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.
LGTM!
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.
Functionality looks great! I left a few minor inline comments.
@tbragin per @cjcenizal's comment are you good with merging this and updating the data table later?
<span | ||
ng-click="inlineFilter($event, '+')" | ||
class="fa fa-search-plus docTableRowFilterIcon" | ||
data-column="<%- column %>" |
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 remove the data-column
and avoid having to inspect the target of the event in the callback by modifying the ng-click like this:
ng-click="inlineFilter('<%= column %>', '-')"
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.
As I mentioned in my other comment, ignore this.
? false | ||
: !mapping[column] | ||
? false | ||
: mapping[column].filterable; |
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 had a difficult time parsing this. Personally I think the following would be more readable:
const isFilterable = !_.isUndefined(flattenedRow[column]) && _.get(mapping, `${column}.filterable`, false);
Thoughts @cjcenizal ?
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 had a few issues with this:
- I had to look up
_.get
to understand what each argument is for. - I also had to hold a lot more state in my head to map input to output (like the double negative of "not undefined").
- Also,
get
returns the default value only forundefined
, but this logic returns false ifmapping[column]
is falsy, so it's not quite a true replacement.
I think the reason I like the ternary is because it has the same pattern as a function with early exits, which makes it really easy for me to map input to output. Maybe the best route is to actually create a function that does just that?
function isFilterable(row, mapping, column) {
if (row[column] === undefined) {
return false;
}
if (!mapping[column]) {
return false;
}
return mapping[column].filterable;
}
What do you think?
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 dislike custom functions where a general one will do. Now I have to find and understand isFilterable
which helps me only in this context. If I don't know how _.get
works, I look it up, and now I know how _.get
works everywhere it's used. It's kind of a "teach a man to fish" sort of thing.
Also, get returns the default value only for undefined, but this logic returns false if mapping[column] is falsy, so it's not quite a true replacement.
First of all, that's not true: http://codepen.io/anon/pen/EZKNNN?editors=0011
But even if it were, false would be the correct value in that situation...
I also had to hold a lot more state in my head to map input to output (like the double negative of "not undefined").
I'd be ok with flattenedRow[column] !== undefined
if you prefer.
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.
Having let it percolate overnight, I think we should just leave the ternary. It was sort of a silly thing to worry about in the first place.
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 it turns out we have an eslint rule against nested ternaries:
/var/lib/jenkins/workspace/elastic+kibana+pull-request+multijob-intake/src/ui/public/doc_table/components/table_row.js
117:13 error Do not nest ternary expressions no-nested-ternary
@jimmyjones2 would you mind implementing the function @cjcenizal suggested above to replace the ternary? Sorry for the back and forth, this should be the last change assuming tests pass.
newHtmls.push(cellTemplate({ | ||
timefield: false, | ||
sourcefield: (column === '_source'), | ||
formatted: _displayField(row, column, true) | ||
formatted: _displayField(row, column, true), |
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.
Let's pass a rawValue
param here (and for the timefield above) and pass it to the inlineFilter
call in the template in the same way I suggested passing the column name. Then the call to flattenHit
can move up to line 99, share the variable throughout createSummaryRow
and it can be removed entirely from inlineFilter
(since the field value will be passed as a param). This way flattenHit only needs to be called once per row.
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.
@Bargs I was worried the raw field value could contain special characters such as quotes - would this be safe to pass into an HTML element attribute and back into inlineFilter unscathed? Just tried a field value containing single and double quotes and it broke.
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.
@jimmyjones2 That's a great point. Ignore both my comments about passing literal values into inlineFilter
in the template.
If you could do me just one other favor, I think this'll be good to merge. Could you add a flattenedRow
attribute to the scope and use that everywhere, rather than re-flattening each time?
@@ -128,7 +150,7 @@ module.directive('kbnTableRow', function ($compile) { | |||
// rebuild cells since we modified the children | |||
$cells = $el.children(); | |||
|
|||
if (i === 0 && !reuse) { | |||
if (!reuse) { |
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.
Curious why this was changed?
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.
@Bargs Can't exactly remember now, but without that change it doesn't work!
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, I looked back in the history, and I guess index 0 is the toggle control which previously was the only element that needed compiled. Now we need to compile the individual cells, so this makes sense.
@Bargs Applied requested changes |
jenkins, test this |
@Bargs I'd personally prefer updating the data table in the same release, so user does not have to deal with two different interactions for some time. So if we merge it, could we not backport until we can get the PR together to get both filtering behaviors to be consistent? @cjcenizal Would you agree? |
@tbragin (Caveat: I don't really have enough experience with interacting with Discover and a Data Table in the same workflow to have a good feeling for this, so I'd weight my opinion about 20%.) Because the Data Table is a smaller piece of the Visualize app, and the table in Discover is a core part of that app's UI, I would be OK with some temporary divergence between the two. If we release in this state, then I think what will happen is:
Would you agree? That doesn't seem so bad to me. |
This is awesome! I would have to agree with @tbragin. Consistency between the doc table and data table is important. |
Sounds like there's enough agreement that we should implement the same functionality in data table before merging this. @jimmyjones2 would you have an interest in taking a crack at it? If not (totally understandable) we can pick it up. |
@Bargs Sorry don't have the time at the moment :( Don't want this PR to languish too much longer though |
No worries @jimmyjones2, I just didn't want to yank the reins from you if you had a burning desire to keep working on it. We'll try to push it it over the line. |
Just put up #9989 to extend this to the data table |
Fixes #3432