Skip to content

Commit 5d1ff3e

Browse files
author
Daniil
committed
[Data Table] Use shared CSV export mechanism (#89702)
* Move formatting columns into response handler * Use shared csv export * Cleanup files * Fix type * Fix translation * Filter out non-dimension values
1 parent 596479a commit 5d1ff3e

18 files changed

+364
-419
lines changed

src/plugins/vis_type_table/public/components/table_vis_basic.tsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,14 @@ import { orderBy } from 'lodash';
1313

1414
import { IInterpreterRenderHandlers } from 'src/plugins/expressions';
1515
import { createTableVisCell } from './table_vis_cell';
16-
import { Table } from '../table_vis_response_handler';
17-
import { TableVisConfig, TableVisUseUiStateProps } from '../types';
18-
import { useFormattedColumnsAndRows, usePagination } from '../utils';
16+
import { TableContext, TableVisConfig, TableVisUseUiStateProps } from '../types';
17+
import { usePagination } from '../utils';
1918
import { TableVisControls } from './table_vis_controls';
2019
import { createGridColumns } from './table_vis_columns';
2120

2221
interface TableVisBasicProps {
2322
fireEvent: IInterpreterRenderHandlers['event'];
24-
table: Table;
23+
table: TableContext;
2524
visConfig: TableVisConfig;
2625
title?: string;
2726
uiStateProps: TableVisUseUiStateProps;
@@ -35,7 +34,7 @@ export const TableVisBasic = memo(
3534
title,
3635
uiStateProps: { columnsWidth, sort, setColumnsWidth, setSort },
3736
}: TableVisBasicProps) => {
38-
const { columns, rows } = useFormattedColumnsAndRows(table, visConfig);
37+
const { columns, rows, formattedColumns } = table;
3938

4039
// custom sorting is in place until the EuiDataGrid sorting gets rid of flaws -> https://github.com/elastic/eui/issues/4108
4140
const sortedRows = useMemo(
@@ -47,13 +46,19 @@ export const TableVisBasic = memo(
4746
);
4847

4948
// renderCellValue is a component which renders a cell based on column and row indexes
50-
const renderCellValue = useMemo(() => createTableVisCell(columns, sortedRows), [
51-
columns,
49+
const renderCellValue = useMemo(() => createTableVisCell(sortedRows, formattedColumns), [
50+
formattedColumns,
5251
sortedRows,
5352
]);
5453

5554
// Columns config
56-
const gridColumns = createGridColumns(table, columns, columnsWidth, sortedRows, fireEvent);
55+
const gridColumns = createGridColumns(
56+
columns,
57+
sortedRows,
58+
formattedColumns,
59+
columnsWidth,
60+
fireEvent
61+
);
5762

5863
// Pagination config
5964
const pagination = usePagination(visConfig, rows.length);
@@ -126,10 +131,9 @@ export const TableVisBasic = memo(
126131
additionalControls: (
127132
<TableVisControls
128133
dataGridAriaLabel={dataGridAriaLabel}
129-
cols={columns}
134+
columns={columns}
130135
// csv exports sorted table
131136
rows={sortedRows}
132-
table={table}
133137
filename={visConfig.title}
134138
/>
135139
),
@@ -138,8 +142,7 @@ export const TableVisBasic = memo(
138142
renderCellValue={renderCellValue}
139143
renderFooterCellValue={
140144
visConfig.showTotal
141-
? // @ts-expect-error
142-
({ colIndex }) => columns[colIndex].formattedTotal || null
145+
? ({ columnId }) => formattedColumns[columnId].formattedTotal || null
143146
: undefined
144147
}
145148
pagination={pagination}

src/plugins/vis_type_table/public/components/table_vis_cell.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,15 @@
99
import React from 'react';
1010
import { EuiDataGridCellValueElementProps } from '@elastic/eui';
1111

12-
import { Table } from '../table_vis_response_handler';
13-
import { FormattedColumn } from '../types';
12+
import { DatatableRow } from 'src/plugins/expressions';
13+
import { FormattedColumns } from '../types';
1414

15-
export const createTableVisCell = (formattedColumns: FormattedColumn[], rows: Table['rows']) => ({
16-
// @ts-expect-error
17-
colIndex,
15+
export const createTableVisCell = (rows: DatatableRow[], formattedColumns: FormattedColumns) => ({
1816
rowIndex,
1917
columnId,
2018
}: EuiDataGridCellValueElementProps) => {
2119
const rowValue = rows[rowIndex][columnId];
22-
const column = formattedColumns[colIndex];
20+
const column = formattedColumns[columnId];
2321
const content = column.formatter.convert(rowValue, 'html');
2422

2523
const cellContent = (

src/plugins/vis_type_table/public/components/table_vis_columns.tsx

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ import React from 'react';
1010
import { EuiDataGridColumnCellActionProps, EuiDataGridColumn } from '@elastic/eui';
1111
import { i18n } from '@kbn/i18n';
1212

13-
import { IInterpreterRenderHandlers } from 'src/plugins/expressions';
14-
import { Table } from '../table_vis_response_handler';
15-
import { FormattedColumn, TableVisUiState } from '../types';
13+
import { DatatableColumn, DatatableRow, IInterpreterRenderHandlers } from 'src/plugins/expressions';
14+
import { FormattedColumns, TableVisUiState } from '../types';
1615

1716
interface FilterCellData {
1817
/**
@@ -27,33 +26,24 @@ interface FilterCellData {
2726
}
2827

2928
export const createGridColumns = (
30-
table: Table,
31-
columns: FormattedColumn[],
29+
columns: DatatableColumn[],
30+
rows: DatatableRow[],
31+
formattedColumns: FormattedColumns,
3232
columnsWidth: TableVisUiState['colWidth'],
33-
rows: Table['rows'],
3433
fireEvent: IInterpreterRenderHandlers['event']
3534
) => {
3635
const onFilterClick = (data: FilterCellData, negate: boolean) => {
37-
/**
38-
* Visible column index and the actual one from the source table could be different.
39-
* e.x. a column could be filtered out if it's not a dimension -
40-
* see formattedColumns in use_formatted_columns.ts file,
41-
* or an extra percantage column could be added, which doesn't exist in the raw table
42-
*/
43-
const rawTableActualColumnIndex = table.columns.findIndex(
44-
(c) => c.id === columns[data.column].id
45-
);
4636
fireEvent({
4737
name: 'filterBucket',
4838
data: {
4939
data: [
5040
{
5141
table: {
52-
...table,
42+
columns,
5343
rows,
5444
},
5545
...data,
56-
column: rawTableActualColumnIndex,
46+
column: data.column,
5747
},
5848
],
5949
negate,
@@ -63,12 +53,13 @@ export const createGridColumns = (
6353

6454
return columns.map(
6555
(col, colIndex): EuiDataGridColumn => {
66-
const cellActions = col.filterable
56+
const formattedColumn = formattedColumns[col.id];
57+
const cellActions = formattedColumn.filterable
6758
? [
6859
({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => {
6960
const rowValue = rows[rowIndex][columnId];
7061
const contentsIsDefined = rowValue !== null && rowValue !== undefined;
71-
const cellContent = col.formatter.convert(rowValue);
62+
const cellContent = formattedColumn.formatter.convert(rowValue);
7263

7364
const filterForText = i18n.translate(
7465
'visTypeTable.tableCellFilter.filterForValueText',
@@ -105,7 +96,7 @@ export const createGridColumns = (
10596
({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => {
10697
const rowValue = rows[rowIndex][columnId];
10798
const contentsIsDefined = rowValue !== null && rowValue !== undefined;
108-
const cellContent = col.formatter.convert(rowValue);
99+
const cellContent = formattedColumn.formatter.convert(rowValue);
109100

110101
const filterOutText = i18n.translate(
111102
'visTypeTable.tableCellFilter.filterOutValueText',
@@ -144,8 +135,8 @@ export const createGridColumns = (
144135
const initialWidth = columnsWidth.find((c) => c.colIndex === colIndex);
145136
const column: EuiDataGridColumn = {
146137
id: col.id,
147-
display: col.title,
148-
displayAsText: col.title,
138+
display: col.name,
139+
displayAsText: col.name,
149140
actions: {
150141
showHide: false,
151142
showMoveLeft: false,

src/plugins/vis_type_table/public/components/table_vis_controls.tsx

Lines changed: 85 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -11,81 +11,103 @@ import { EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } f
1111
import { FormattedMessage } from '@kbn/i18n/react';
1212
import { i18n } from '@kbn/i18n';
1313

14-
import { DatatableRow } from 'src/plugins/expressions';
14+
import { DatatableColumn, DatatableRow } from 'src/plugins/expressions';
1515
import { CoreStart } from 'kibana/public';
1616
import { useKibana } from '../../../kibana_react/public';
17-
import { FormattedColumn } from '../types';
18-
import { Table } from '../table_vis_response_handler';
19-
import { exportAsCsv } from '../utils';
17+
import { exporters } from '../../../data/public';
18+
import {
19+
CSV_SEPARATOR_SETTING,
20+
CSV_QUOTE_VALUES_SETTING,
21+
downloadFileAs,
22+
} from '../../../share/public';
23+
import { getFormatService } from '../services';
2024

2125
interface TableVisControlsProps {
2226
dataGridAriaLabel: string;
2327
filename?: string;
24-
cols: FormattedColumn[];
28+
columns: DatatableColumn[];
2529
rows: DatatableRow[];
26-
table: Table;
2730
}
2831

29-
export const TableVisControls = memo(({ dataGridAriaLabel, ...props }: TableVisControlsProps) => {
30-
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
31-
const togglePopover = useCallback(() => setIsPopoverOpen((state) => !state), []);
32-
const closePopover = useCallback(() => setIsPopoverOpen(false), []);
32+
export const TableVisControls = memo(
33+
({ dataGridAriaLabel, filename, columns, rows }: TableVisControlsProps) => {
34+
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
35+
const togglePopover = useCallback(() => setIsPopoverOpen((state) => !state), []);
36+
const closePopover = useCallback(() => setIsPopoverOpen(false), []);
3337

34-
const {
35-
services: { uiSettings },
36-
} = useKibana<CoreStart>();
38+
const {
39+
services: { uiSettings },
40+
} = useKibana<CoreStart>();
3741

38-
const onClickExport = useCallback(
39-
(formatted: boolean) =>
40-
exportAsCsv(formatted, {
41-
...props,
42-
uiSettings,
43-
}),
44-
[props, uiSettings]
45-
);
42+
const onClickExport = useCallback(
43+
(formatted: boolean) => {
44+
const csvSeparator = uiSettings.get(CSV_SEPARATOR_SETTING);
45+
const quoteValues = uiSettings.get(CSV_QUOTE_VALUES_SETTING);
4646

47-
const exportBtnAriaLabel = i18n.translate('visTypeTable.vis.controls.exportButtonAriaLabel', {
48-
defaultMessage: 'Export {dataGridAriaLabel} as CSV',
49-
values: {
50-
dataGridAriaLabel,
51-
},
52-
});
47+
const content = exporters.datatableToCSV(
48+
{
49+
type: 'datatable',
50+
columns,
51+
rows,
52+
},
53+
{
54+
csvSeparator,
55+
quoteValues,
56+
formatFactory: getFormatService().deserialize,
57+
raw: !formatted,
58+
}
59+
);
60+
downloadFileAs(`${filename || 'unsaved'}.csv`, { content, type: exporters.CSV_MIME_TYPE });
61+
},
62+
[columns, rows, filename, uiSettings]
63+
);
5364

54-
const button = (
55-
<EuiButtonEmpty
56-
aria-label={exportBtnAriaLabel}
57-
size="xs"
58-
iconType="exportAction"
59-
color="text"
60-
className="euiDataGrid__controlBtn"
61-
onClick={togglePopover}
62-
>
63-
<FormattedMessage id="visTypeTable.vis.controls.exportButtonLabel" defaultMessage="Export" />
64-
</EuiButtonEmpty>
65-
);
65+
const exportBtnAriaLabel = i18n.translate('visTypeTable.vis.controls.exportButtonAriaLabel', {
66+
defaultMessage: 'Export {dataGridAriaLabel} as CSV',
67+
values: {
68+
dataGridAriaLabel,
69+
},
70+
});
6671

67-
const items = [
68-
<EuiContextMenuItem key="rawCsv" onClick={() => onClickExport(false)}>
69-
<FormattedMessage id="visTypeTable.vis.controls.rawCSVButtonLabel" defaultMessage="Raw" />
70-
</EuiContextMenuItem>,
71-
<EuiContextMenuItem key="csv" onClick={() => onClickExport(true)}>
72-
<FormattedMessage
73-
id="visTypeTable.vis.controls.formattedCSVButtonLabel"
74-
defaultMessage="Formatted"
75-
/>
76-
</EuiContextMenuItem>,
77-
];
72+
const button = (
73+
<EuiButtonEmpty
74+
aria-label={exportBtnAriaLabel}
75+
size="xs"
76+
iconType="exportAction"
77+
color="text"
78+
className="euiDataGrid__controlBtn"
79+
onClick={togglePopover}
80+
>
81+
<FormattedMessage
82+
id="visTypeTable.vis.controls.exportButtonLabel"
83+
defaultMessage="Export"
84+
/>
85+
</EuiButtonEmpty>
86+
);
7887

79-
return (
80-
<EuiPopover
81-
id="dataTableExportData"
82-
button={button}
83-
isOpen={isPopoverOpen}
84-
closePopover={closePopover}
85-
panelPaddingSize="none"
86-
repositionOnScroll
87-
>
88-
<EuiContextMenuPanel className="eui-textNoWrap" items={items} />
89-
</EuiPopover>
90-
);
91-
});
88+
const items = [
89+
<EuiContextMenuItem key="rawCsv" onClick={() => onClickExport(false)}>
90+
<FormattedMessage id="visTypeTable.vis.controls.rawCSVButtonLabel" defaultMessage="Raw" />
91+
</EuiContextMenuItem>,
92+
<EuiContextMenuItem key="csv" onClick={() => onClickExport(true)}>
93+
<FormattedMessage
94+
id="visTypeTable.vis.controls.formattedCSVButtonLabel"
95+
defaultMessage="Formatted"
96+
/>
97+
</EuiContextMenuItem>,
98+
];
99+
100+
return (
101+
<EuiPopover
102+
id="dataTableExportData"
103+
button={button}
104+
isOpen={isPopoverOpen}
105+
closePopover={closePopover}
106+
panelPaddingSize="none"
107+
repositionOnScroll
108+
>
109+
<EuiContextMenuPanel className="eui-textNoWrap" items={items} />
110+
</EuiPopover>
111+
);
112+
}
113+
);

src/plugins/vis_type_table/public/components/table_vis_split.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
import React, { memo } from 'react';
1010

1111
import { IInterpreterRenderHandlers } from 'src/plugins/expressions';
12-
import { TableGroup } from '../table_vis_response_handler';
13-
import { TableVisConfig, TableVisUseUiStateProps } from '../types';
12+
import { TableGroup, TableVisConfig, TableVisUseUiStateProps } from '../types';
1413
import { TableVisBasic } from './table_vis_basic';
1514

1615
interface TableVisSplitProps {
@@ -24,11 +23,11 @@ export const TableVisSplit = memo(
2423
({ fireEvent, tables, visConfig, uiStateProps }: TableVisSplitProps) => {
2524
return (
2625
<>
27-
{tables.map(({ tables: dataTable, key, title }) => (
28-
<div key={key} className="tbvChart__split">
26+
{tables.map(({ table, title }) => (
27+
<div key={title} className="tbvChart__split">
2928
<TableVisBasic
3029
fireEvent={fireEvent}
31-
table={dataTable[0]}
30+
table={table}
3231
visConfig={visConfig}
3332
title={title}
3433
uiStateProps={uiStateProps}

src/plugins/vis_type_table/public/components/table_visualization.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,15 @@ import { CoreStart } from 'kibana/public';
1414
import { IInterpreterRenderHandlers } from 'src/plugins/expressions';
1515
import type { PersistedState } from 'src/plugins/visualizations/public';
1616
import { KibanaContextProvider } from '../../../kibana_react/public';
17-
import { TableVisConfig } from '../types';
18-
import { TableContext } from '../table_vis_response_handler';
17+
import { TableVisConfig, TableVisData } from '../types';
1918
import { TableVisBasic } from './table_vis_basic';
2019
import { TableVisSplit } from './table_vis_split';
2120
import { useUiState } from '../utils';
2221

2322
interface TableVisualizationComponentProps {
2423
core: CoreStart;
2524
handlers: IInterpreterRenderHandlers;
26-
visData: TableContext;
25+
visData: TableVisData;
2726
visConfig: TableVisConfig;
2827
}
2928

0 commit comments

Comments
 (0)