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][Datatable] Fix share export and inspect data #193780

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

nickofthyme
Copy link
Contributor

@nickofthyme nickofthyme commented Sep 23, 2024

Summary

This PR fixes #187551 where the table provided in the inspector and the share export, did not match what was visible in the table.

Changes

Viewing the datatable from Inspect action

Zight.Recording.2024-09-23.at.02.06.10.PM.mp4

Exporting table to csv via the Share action

  • Now exports only the transposed data table.
  • Fixes column ordering of transposed data. Transposed metrics are zipped together by like terms (i.e. ['css › X', 'css › Y', 'deb › X', 'deb › Y']).
  • Now sorts rows based on active column sorting parameters (only for export not inspect). Always places null values at the bottom to match eui behavior.
image

The Lens table above would output a csv that shows as shown below (in Numbers).

image

Or in raw form as.

"Top 3 values of agent.keyword","(empty) › Count of records","gz › Count of records","css › Count of records","zip › Count of records","deb › Count of records"
"Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1",268,131,122,81,70
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24",235,109,98,81,68
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)",210,93,82,64,52

Checklist

- Add transposed table to list of tables
- Fix table select dropdown styles
- export transpose datatable from share
- support sorting transposed columns
- sort transposed columns by args column order (zippered)
- removed hidden columns from share output
- sort rows when column sorting is present
- push empty value to bottom
- Set transposed table as default selection in inspector
- Export only transposed table on share
- Remove unused prop-types
@elasticmachine
Copy link
Contributor

🤖 Jobs for this PR can be triggered through checkboxes. 🚧

ℹ️ To trigger the CI, please tick the checkbox below 👇

  • Click to trigger kibana-pull-request for this PR!

@nickofthyme nickofthyme added Team:Visualizations Visualization editors, elastic-charts and infrastructure Feature:Lens labels Sep 23, 2024
Copy link
Contributor Author

@nickofthyme nickofthyme left a comment

Choose a reason for hiding this comment

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

Comments to reviewers

Comment on lines +81 to +111
<EuiFlexItem>
<div>
<EuiPopover
id="inspectorTableChooser"
button={
<EuiButtonEmpty
iconType="arrowDown"
iconSide="right"
size="s"
onClick={this.togglePopover}
data-test-subj="inspectorTableChooser"
>
<FormattedMessage
id="data.inspector.table.inspectorTableChooserButton"
defaultMessage="Table {index}"
values={{ index: currentIndex + 1 }}
/>
</EuiButtonEmpty>
}
isOpen={this.state.isPopoverOpen}
closePopover={this.closePopover}
panelPaddingSize="none"
anchorPosition="downLeft"
repositionOnScroll
>
<EuiContextMenuPanel
items={this.props.tables.map(this.renderTableDropdownItem)}
data-test-subj="inspectorTableChooserMenuPanel"
/>
</EuiPopover>
</div>
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 fixed the full width dropdown placement...

Before

Zight Recording 2024-09-20 at 04 17 53 PM

After

Zight Recording 2024-09-20 at 04 16 55 PM

src/plugins/data/common/exports/export_csv.tsx Outdated Show resolved Hide resolved
src/plugins/data/common/exports/export_csv.tsx Outdated Show resolved Hide resolved
Comment on lines 26 to 27
sortedColumns?: string[];
columnSorting?: EuiDataGridColumnSortingConfig[];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here we pass both the sorted columns and now any column sorting config the user has defined.

Copy link
Member

Choose a reason for hiding this comment

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

the EuiDataGridColumnSortingConfig is an array of id and direction, can we just use this instead of having both the sortedColumns and this array?
We can sort that configs array the same way as the sortedColums

Copy link
Contributor Author

@nickofthyme nickofthyme Sep 24, 2024

Choose a reason for hiding this comment

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

Are you saying to combine them such that the order of the columnSorting matches that of the sortedColumns?

// do this...
const columnSorting = [{ id: 'col-a', direction: 'none'}, { id: 'col-b', direction: 'desc'}];
// instead of this...
const sortedColumns = ['col-a', 'col-b'];
const columnSorting = [{ id: 'col-b', direction: 'desc'}];

In that case I don't think so. The columnSorting is already an array who's order indicates the hierarchy of multiple filters. This is the same behavior in eui. Even though the current Table vis only supports a singular column sort, I don't think it would be a good idea.

Even in terms of usability of this function, I think it is much easier on the user as is rather than having to combine them, let alone understand what the order means.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed this option in favor of sorting the datatable prior to passing to this util. See 44baf77.

Comment on lines +89 to +92
const colOrderMap = new Map(args.columns.map((c, i) => [c.columnId, i]));
firstTable.columns.sort((a, b) => {
return (colOrderMap.get(a.id) ?? 0) - (colOrderMap.get(b.id) ?? 0);
});
Copy link
Contributor Author

@nickofthyme nickofthyme Sep 23, 2024

Choose a reason for hiding this comment

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

Aligns the table column order to that of the args. This is to done to preserve the order of transposed columns as described in #193780 (comment).

Copy link
Contributor

@dej611 dej611 left a comment

Choose a reason for hiding this comment

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

Initial code review

) {
if (!columnSorting) return () => 0;

const columnIdMap = new Map(columnSorting.map(({ id }) => [id, sortedColumnIds.indexOf(id)]));
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 quadratic. Can we make leverage a lookup map?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed in 44baf77, in favor of @kbn/sort-predicates utilities.

Comment on lines 48 to 58
const sortedIds = sortedColumns
? columns
.map((c) => {
// need to find original id for transposed column
const sortIndex = sortedColumns.findIndex((id) => c.id.endsWith(id));
return {
id: c.id,
sortIndex,
isTransposed: (sortedColumns[sortIndex] ?? '') !== c.id,
};
})
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 quadratic in terms of time.
Can you use a lookup table here in order to reduce this from N^2 to 2 * N?

I think the transposed logic should not be exposed here, rather the sortedColumns should be computed on the Lens side where it is aaware of the transposed state, and able to extract the original id with the proper function 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.

Removed column sorting logic from export logic in dfc2221

if (!columnSorting) return () => 0;

const columnIdMap = new Map(columnSorting.map(({ id }) => [id, sortedColumnIds.indexOf(id)]));
return (rowA: string[], rowB: string[]) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Have you considered leveraging the getSortingCriteria from the @kbn/sort-predicates package used by the Lens datatable? This will ensure consistency between the two.

Can find an example usage here: x-pack/plugins/lens/public/visualizations/datatable/components/table_actions.ts

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 was a great call, took a bit of time to wrangle all the necessary inputs but finally got it working. Updated in 44baf77.

- used sorted column order from state
- use column order from transposed table via activeData
- remove transpose logic from csv export logic
- use prepareLogTable to strip out ghost columns ids from formulas
…rted to the csv export utility

- move column & row sorting into vis definition
- use `@kbn/sort-predicates` to sort rows
- remove sorted columns logic
- remove column sorting logic
Comment on lines +615 to 619
const datatables =
exportDatatables.length > 0 ? exportDatatables : Object.values(activeData ?? {});
const sharingData = {
datatables,
csvEnabled,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fallback to activeData if no tables are provided. Not sure there there could be a need to want to export no data.

Comment on lines 22 to 37
raw?: boolean;
columnsSorting?: string[];
}

export function datatableToCSV(
{ columns, rows }: Datatable,
{ csvSeparator, quoteValues, formatFactory, raw, escapeFormulaValues, columnsSorting }: CSVOptions
{ csvSeparator, quoteValues, formatFactory, raw, escapeFormulaValues }: CSVOptions
) {
const escapeValues = createEscapeValue({
separator: csvSeparator,
quoteValues,
escapeFormulaValues,
});

const sortedIds = columnsSorting || columns.map((col) => col.id);

// Build an index lookup table
const columnIndexLookup = sortedIds.reduce((memo, id, index) => {
memo[id] = index;
return memo;
}, {} as Record<string, number>);

// Build the header row by its names
const header: string[] = [];
const sortedColumnIds: string[] = [];
const formatters: Record<string, ReturnType<FormatFactory>> = {};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@dej611 I removed the column sort logic from here altogether. I was trying to find the best approach based on what we talked about and I think instead of having the vis provide the sort order of the columns we just have the vis provide the data to be exported.

This was the columns and rows are sorted as needed in the new vis.getExportDatatables method.

Additionally, the downloadCsvShareProvider.getShareMenuItems loads all tables from the activeData, so when there are multiple it is confusing to have a single list of sorted columns. Now if a vis needs to export more than one datatable, it can sort/filter any active datatables before passing them to the exporter.

LMK what you think...

Comment on lines +98 to +101
const colOrderMap = new Map(args.columns.map((c, i) => [c.columnId, i]));
firstTable.columns.sort((a, b) => {
return (colOrderMap.get(a.id) ?? 0) - (colOrderMap.get(b.id) ?? 0);
});
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 believe this fixes #164413 as an alternate to sorting columns in csv exporter.

@elasticmachine
Copy link
Contributor

elasticmachine commented Oct 18, 2024

💔 Build Failed

Failed CI Steps

Test Failures

  • [job] [logs] Jest Tests #14 / CSV exporter should respect the sorted columns order when passed
  • [job] [logs] Jest Tests #14 / CSV exporter should respect the sorted columns order when passed
  • [job] [logs] Jest Integration Tests #1 / incompatible_cluster_routing_allocation retries the INIT action with a descriptive message when cluster settings are incompatible
  • [job] [logs] Jest Integration Tests #1 / incompatible_cluster_routing_allocation retries the INIT action with a descriptive message when cluster settings are incompatible
  • [job] [logs] Jest Tests #14 / Inspector Data View component should render loading state
  • [job] [logs] Jest Tests #14 / Inspector Data View component should render loading state
  • [job] [logs] Jest Tests #14 / Inspector Data View component should support multiple datatables
  • [job] [logs] Jest Tests #14 / Inspector Data View component should support multiple datatables
  • [job] [logs] FTR Configs #13 / lens app - group 2 lens datatable should show dynamic coloring feature for numeric columns
  • [job] [logs] FTR Configs #13 / lens app - group 2 lens datatable should show dynamic coloring feature for numeric columns
  • [job] [logs] Jest Integration Tests #1 / migration v2 - read batch size does not reduce the read batchSize in half if no batches exceeded maxReadBatchSizeBytes
  • [job] [logs] Jest Integration Tests #1 / migration v2 - read batch size does not reduce the read batchSize in half if no batches exceeded maxReadBatchSizeBytes
  • [job] [logs] Jest Integration Tests #1 / migration v2 - read batch size reduces the read batchSize in half if a batch exceeds maxReadBatchSizeBytes
  • [job] [logs] Jest Integration Tests #1 / migration v2 - read batch size reduces the read batchSize in half if a batch exceeds maxReadBatchSizeBytes
  • [job] [logs] Jest Integration Tests #1 / migration v2 migrates saved objects normally with multiple ES nodes
  • [job] [logs] Jest Integration Tests #1 / migration v2 migrates saved objects normally with multiple ES nodes
  • [job] [logs] Jest Tests #17 / transpose helpers should transpose table by one column
  • [job] [logs] Jest Tests #17 / transpose helpers should transpose table by one column
  • [job] [logs] Jest Tests #17 / transpose helpers should transpose table by two columns
  • [job] [logs] Jest Tests #17 / transpose helpers should transpose table by two columns

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
expressionHeatmap 178 179 +1
lens 1465 1461 -4
total -3

Public APIs missing comments

Total count of every public API that lacks a comment. Target amount is 0. Run node scripts/build_api_docs --plugin [yourplugin] --stats comments for more detailed information.

id before after diff
@kbn/transpose-helpers - 10 +10
expressions 1765 1769 +4
lens 589 590 +1
total +15

Async chunks

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

id before after diff
data 48.0KB 48.1KB +98.0B
expressionHeatmap 26.7KB 26.8KB +62.0B
lens 1.5MB 1.5MB +2.9KB
total +3.1KB

Public APIs missing exports

Total count of every type that is part of your API that should be exported but is not. This will cause broken links in the API documentation system. Target amount is 0. Run node scripts/build_api_docs --plugin [yourplugin] --stats exports for more detailed information.

id before after diff
lens 62 64 +2

Page load bundle

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

id before after diff
data 419.8KB 419.8KB -68.0B
expressions 99.8KB 100.3KB +434.0B
lens 51.6KB 50.7KB -847.0B
total -481.0B
Unknown metric groups

API count

id before after diff
@kbn/transpose-helpers - 13 +13
expressions 2235 2241 +6
lens 691 692 +1
total +20

History

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:Lens Team:Visualizations Visualization editors, elastic-charts and infrastructure
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Lens] Fix issue downloading tables to CSV that contain "Split metrics by"
5 participants