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

[Lens] New sorting feature for the datatable visualization #84435

Merged
merged 20 commits into from
Dec 15, 2020

Conversation

dej611
Copy link
Contributor

@dej611 dej611 commented Nov 26, 2020

Summary

Fixes #76962

This PR implements the sorting feature for the datatable visualization in Lens.

Rather than using the Canvas sort function the lodash sorting implementation has been used in here: there's a compatibility issue between the MultiDatatable format and the Datatable accepted as input.

The feature reads the renderMode property to become interactive, and its stored in the state to be shown when in display mode.

sorting

sorting_dashboard

Styling can probably be improved: a feature request has been submitted for a new readOnly state for Table sorting, in order to improve the View mode look and feel.

Checklist

@dej611 dej611 marked this pull request as ready for review December 1, 2020 17:37
@dej611 dej611 requested a review from a team December 1, 2020 17:37
@dej611
Copy link
Contributor Author

dej611 commented Dec 1, 2020

@elasticmachine merge upstream

@dej611 dej611 added the Team:Visualizations Visualization editors, elastic-charts and infrastructure label Dec 1, 2020
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-app (Team:KibanaApp)

Copy link
Contributor

@wylieconlon wylieconlon left a comment

Choose a reason for hiding this comment

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

Tested in Chrome and it's working consistently with Visualize. Accessibility looks good.

Can you think about how the title of this PR would appear in release notes, and whether we need to document this as part of the existing docs?

The "download as CSV" was not sorted- it makes sense, but is there something we can do to improve this?

let sortedRows = rows;

if (sortBy && sortDirection !== 'none') {
sortedRows = orderBy(rows, [sortBy], sortDirection as Direction);
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like we are sorting based on the unformatted value of the table cell, not the formatted value. I checked and this is the existing behavior of the Visualize app, but is this the best behavior? It does appear to be correct for numbers and dates using the standard formatters.

The buggiest case I could find is how we deal with IP address fields- the sort order looks totally wrong because it's sorting the unformatted value. What do you think the options are?

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 is a good point. I'll improve on this side. 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

@dej611 I would expect numbers and dates to be sorted by raw value and strings sorted by the formatted value (e.g. for stuff like static lookup formatters) .

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What about other types? Using formatters for everything but numbers and dates?

sorting?: {
columnId: string | undefined;
direction: 'asc' | 'desc';
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is the sorting attached to the top level of the state, instead of attached to a specific layer?

Also, what happens in these two cases?

  1. A sorted column is deleted- is the state correctly updated?
  2. The columnId for a sorted column is reused but the function has changed. It's still sorted, but should it be?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I've thought about that and discussed a bit with @flash1293 about.
We agreed to have sorting on a visualization level for now, as tables can only have one layer.
When we'll extend tables support for multiple layer this feature will be revisited, as probably other changes are planned to happen to this code meanwhile.

Copy link
Contributor

Choose a reason for hiding this comment

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

No strong feeling about the sorting state - @dej611 and me discussed this but I think it's fine in both places. I wasn't aware we are supporting multiple layers in the first place, what's the reasoning for this? (it seems like it's not possible for the user to actually configure this and even if they did, it would just show the first table)

The columnId for a sorted column is reused but the function has changed. It's still sorted, but should it be?

IMHO it should still be sorted as long as it's the same dimension/column. If the dimension gets deleted completely, the sorting state should be deleted along with it.

Copy link
Contributor Author

@dej611 dej611 Dec 2, 2020

Choose a reason for hiding this comment

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

  • A sorted column is deleted- is the state correctly updated?

Probably the state in this case will remain "dirty" but the final result should be correct anyway as it relies on the columnId. I'll try to address this scenario.

  • The columnId for a sorted column is reused but the function has changed. It's still sorted, but should it be?

I wasn't aware of recycled ids. I'll take care for that too.

I think it still makes sense to have the sorting on that column, even when the operation is changed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Had a first look at this implementation: the formatters are available only at render time as dependency, not at expression time, going in conflict on the CSV sorted downloadable content.

I'll investigate on possible solutions. In case I'd rather prioritise this over the CSV sorted downloadable content.

Copy link
Contributor

@flash1293 flash1293 Dec 2, 2020

Choose a reason for hiding this comment

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

Ah, good point. Agreed, we can open a separate issue for CSV sorted downloadable content. It's fixable by passing down the reference to the format factory from data plugin from the setup method of the DatatableVisualization service class in x-pack/plugins/lens/public/datatable_visualization/index.ts and wrapping the datatable visualization definition into a getter (getDatatableVisualizationDefinition(data: DataPublicPluginStart)). We are doing a similar thing in xy chart for passing down the palette service.

@@ -608,6 +608,17 @@ export interface LensBrushEvent {
data: TriggerContext<typeof SELECT_RANGE_TRIGGER>['data'];
}

export interface LensSortActionData {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you move this type into the datatable visualization and make LensEditEvent a generic type for all kinds of payloads?

@flash1293
Copy link
Contributor

The "download as CSV" was not sorted- it makes sense, but is there something we can do to improve this?

That's a good point - we can actually improve this by moving the sorting logic into the datatable expression function instead of the renderer and updating the tables adapter with the sorted table.

@flash1293
Copy link
Contributor

I didn't find a good way to "hide" the sorting functionality when the table is used on a dashboard - I'm mostly concerned about the sorting being explicitly called out via screenreader along with the visual indicators:
Screenshot 2020-12-02 at 10 25 42

IMHO it's worth addressing this upstream before merging the PR.

@dej611
Copy link
Contributor Author

dej611 commented Dec 3, 2020

Implemented a basic readOnly state for the sorting header.
The sorting header is yet not keyboard accessible: it is already an hacky solution, not sure if we want to keep going this direction. Even a small fix like elastic/eui#4333 may help on this side.

As an alternative I've also tried with a tooltip when in readOnly mode: it offers a cleaner solution, but then introduces inconsistencies with the edit mode. Unless we want to completely handle the sorting feature via custom tooltips

Copy link
Contributor

@flash1293 flash1293 left a comment

Choose a reason for hiding this comment

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

I would be fine with this solution a11y-wise, but I would like to get @myasonik s expertise on this:

Michail, we want to show a sorted table and indicate that, but the user shouldn't be able to change the sorting. Unfortunately this is currently not supported by EuiBasicTable.

This PR implements a workaround by not telling the table it's sorting, but showing the icon and a screenreader text in the header cell manually.

Header cell during edit mode (default created by EUI component):
Screenshot 2020-12-03 at 14 19 50

Header cell during view mode:
Screenshot 2020-12-03 at 14 22 59

The only thing missing AFAICT is the aria-sort attribute on the th element (it's on the span within the th, but not sure whether it helps much there). The question is whether that's a big no-no or whether it's just not perfect. If it's a minor issue, we would like to get the feature into 7.11, otherwise we will take care of an upstream fix in EUI to do it right from the start.

You can test this by creating a Lens table and clicking the header to sort the table, then saving it to a dashboard.

@@ -25,6 +25,7 @@ import {
VALUE_CLICK_TRIGGER,
VisualizeFieldContext,
} from '../../../../src/plugins/ui_actions/public';
import { LensSortActionData, LENS_EDIT_SORT_ACTION } from './datatable_visualization/expression';
Copy link
Contributor

Choose a reason for hiding this comment

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

This import is causing the current build to fail because all of the datatable vis is included in the initial bundle. This should be fixable by making it a type import: import type { LensSortActionData, LENS_EDIT_SORT_ACTION } from './datatable_visualization/expression';

}
// This is a workaround to hijack the title value of the header cell
return (
<span aria-sort={getDirectionLongLabel(sorting.direction)} aria-live="polite">
Copy link
Contributor

Choose a reason for hiding this comment

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

This won't update, so no need to make it aria-live="polite"

columnsReverseLookup[sortBy]?.meta?.type || ''
)
? sortBy
: (row: Record<string, unknown>) => formatters[sortBy]?.convert(row[sortBy]);
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not taking into account ip sorting, right? If Visualize datatable isn't doing it right either at the moment, I'm fine with resolving this later, but we should create an issue for 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.

Update a first version of IPv4 sorting specific logic

@myasonik
Copy link
Contributor

myasonik commented Dec 3, 2020

<sorting workaround for view-only tables>

IMO it's a minor issue. Screenreaders will read out the table sort better* when using aria-sort but this seems like a minor bug. If there's an issue for it in EUI to eventually get resolved, I'm not worried about this shipping.

As another workaround which may be uglier in the code but would be better for a11y: you could use a ref pointed to the table, query for the header cell, then headerCell.setAttribute('aria-sort', ${sortDirection}) to get the right attribute on the right element but I'm not sure how much overhead that might add.

*What does better mean? Better just means assistive tech can/will read out the sort when entering the table from anywhere (jumping into it, going backwards into it, etc), whereas with the current solution a user wouldn't know the table is sorted without reading the column header.

@flash1293
Copy link
Contributor

As another workaround which may be uglier in the code but would be better for a11y: you could use a ref pointed to the table, query for the header cell, then headerCell.setAttribute('aria-sort', ${sortDirection}) to get the right attribute on the right element but I'm not sure how much overhead that might add.

This scares me, if we can live without it please let us do :)

@dej611 @wylieconlon I'd say let's try to get this merged before 7.11 as it seems very close to me. I can give it another look tomorrow.

Copy link
Contributor

@flash1293 flash1293 left a comment

Choose a reason for hiding this comment

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

As discussed, the logic for changing the sort order should be moved inside of the datatable visualization, keeping the frame level code agnostic to specific events being fired.

Also, visualizationState should not re-create the handleEvent callback (this can be done by allowing an updater function to be used for visualization)

if (type !== 'ip') {
return rowFormatted;
}
// Note this supports only IPv4: maybe there already a solution to support IPv6 and more?
Copy link
Contributor

Choose a reason for hiding this comment

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

Ip fields can also store ipv6 values in Elasticsearch: https://www.elastic.co/guide/en/elasticsearch/reference/current/ip.html

To me it feels weird to just support ip4, but always attempt to sort. IMHO we should split that feature out and disable sorting on ip columns for now and tackle that separately.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, agree. There are also special values we may want to handle (localhost as/or 127.0.0.1?).

Copy link
Contributor

@flash1293 flash1293 left a comment

Choose a reason for hiding this comment

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

This looks great to me!

The only thing I'm concerned about is the ip sorting, I left a comment about that.

Otherwise this LGTM.

Edit: Just discovered another edge case where it doesn't work correctly - ranges:
Screenshot 2020-12-04 at 17 16 28

I think they should get sorted by the from, then by the to, but I'm not a fan of tying the datatable sorting algorithm to ranges specifically. I can't think of a better solution though - so for now I would be fine with checking whether a number column contains objects with a from or a to property and sort by them.

@dej611
Copy link
Contributor Author

dej611 commented Dec 4, 2020

I think they should get sorted by the from, then by the to, but I'm not a fan of tying the datatable sorting algorithm to ranges specifically. I can't think of a better solution though - so for now I would be fine with checking whether a number column contains objects with a from or a to property and sort by them.

Tried to implement this but it's not super trivial. I would rather keep the string comparison for the moment and probably build/get a tiny utility well tested for that.

I could still remove the IPv4 sorting logic if prefer to delay for a better solution.

@flash1293
Copy link
Contributor

I would go with the following:

  • Removing sorting support for ip completely (not even string based) - just don't make the column header clickable in that case. EuiBasicTable allows to configure this
  • Live with string sorting for ranges for now because we have no good way of handling the case, but create an issue to clean this up eventually

@flash1293
Copy link
Contributor

@elasticmachine merge upstream

@flash1293
Copy link
Contributor

@elasticmachine merge upstream

@flash1293
Copy link
Contributor

As discussed offline, for the first version we will just allow sorting ips and ranges and sort them as strings. Improving this will become a fix-it-week issue.

Copy link
Contributor

@flash1293 flash1293 left a comment

Choose a reason for hiding this comment

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

Tested and everything works as expected, LGTM.

@dej611
Copy link
Contributor Author

dej611 commented Dec 14, 2020

#83167 changed some types in the EuiBasicTable component we had, generating some conflicts. Had to revisit few types to make it work with sorting as well.

Copy link
Contributor

@wylieconlon wylieconlon left a comment

Choose a reason for hiding this comment

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

Tested the new behavior and LGTM, left some style comments

state:
typeof action.updater === 'function'
? action.updater(state.visualization.state)
: action.updater,
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a good change, but does it need to be in this PR specifically?

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 PR was the place we realised about this possible change. Making it here helped fixing a useEffect side effect which was affecting the datatable visualization.

@kibanamachine
Copy link
Contributor

💚 Build Succeeded

Metrics [docs]

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
lens 1018.4KB 1023.4KB +5.0KB

Distributable file count

id before after diff
default 47130 47890 +760

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
lens 53.7KB 54.1KB +404.0B

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@dej611 dej611 merged commit 62623cd into elastic:master Dec 15, 2020
@dej611 dej611 deleted the feature/lens/table-sorting branch December 15, 2020 11:38
dej611 added a commit to dej611/kibana that referenced this pull request Dec 15, 2020
dej611 added a commit that referenced this pull request Dec 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:Lens release_note:enhancement Team:Visualizations Visualization editors, elastic-charts and infrastructure v7.11.0 v8.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Lens] Client side table sorting
6 participants