Skip to content

Commit

Permalink
[Data Table] Use shared CSV export mechanism (elastic#89702)
Browse files Browse the repository at this point in the history
* Move formatting columns into response handler

* Use shared csv export

* Cleanup files

* Fix type

* Fix translation

* Filter out non-dimension values
  • Loading branch information
Daniil committed Feb 2, 2021
1 parent 596479a commit 5d1ff3e
Show file tree
Hide file tree
Showing 18 changed files with 364 additions and 419 deletions.
27 changes: 15 additions & 12 deletions src/plugins/vis_type_table/public/components/table_vis_basic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,14 @@ import { orderBy } from 'lodash';

import { IInterpreterRenderHandlers } from 'src/plugins/expressions';
import { createTableVisCell } from './table_vis_cell';
import { Table } from '../table_vis_response_handler';
import { TableVisConfig, TableVisUseUiStateProps } from '../types';
import { useFormattedColumnsAndRows, usePagination } from '../utils';
import { TableContext, TableVisConfig, TableVisUseUiStateProps } from '../types';
import { usePagination } from '../utils';
import { TableVisControls } from './table_vis_controls';
import { createGridColumns } from './table_vis_columns';

interface TableVisBasicProps {
fireEvent: IInterpreterRenderHandlers['event'];
table: Table;
table: TableContext;
visConfig: TableVisConfig;
title?: string;
uiStateProps: TableVisUseUiStateProps;
Expand All @@ -35,7 +34,7 @@ export const TableVisBasic = memo(
title,
uiStateProps: { columnsWidth, sort, setColumnsWidth, setSort },
}: TableVisBasicProps) => {
const { columns, rows } = useFormattedColumnsAndRows(table, visConfig);
const { columns, rows, formattedColumns } = table;

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

// renderCellValue is a component which renders a cell based on column and row indexes
const renderCellValue = useMemo(() => createTableVisCell(columns, sortedRows), [
columns,
const renderCellValue = useMemo(() => createTableVisCell(sortedRows, formattedColumns), [
formattedColumns,
sortedRows,
]);

// Columns config
const gridColumns = createGridColumns(table, columns, columnsWidth, sortedRows, fireEvent);
const gridColumns = createGridColumns(
columns,
sortedRows,
formattedColumns,
columnsWidth,
fireEvent
);

// Pagination config
const pagination = usePagination(visConfig, rows.length);
Expand Down Expand Up @@ -126,10 +131,9 @@ export const TableVisBasic = memo(
additionalControls: (
<TableVisControls
dataGridAriaLabel={dataGridAriaLabel}
cols={columns}
columns={columns}
// csv exports sorted table
rows={sortedRows}
table={table}
filename={visConfig.title}
/>
),
Expand All @@ -138,8 +142,7 @@ export const TableVisBasic = memo(
renderCellValue={renderCellValue}
renderFooterCellValue={
visConfig.showTotal
? // @ts-expect-error
({ colIndex }) => columns[colIndex].formattedTotal || null
? ({ columnId }) => formattedColumns[columnId].formattedTotal || null
: undefined
}
pagination={pagination}
Expand Down
10 changes: 4 additions & 6 deletions src/plugins/vis_type_table/public/components/table_vis_cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,15 @@
import React from 'react';
import { EuiDataGridCellValueElementProps } from '@elastic/eui';

import { Table } from '../table_vis_response_handler';
import { FormattedColumn } from '../types';
import { DatatableRow } from 'src/plugins/expressions';
import { FormattedColumns } from '../types';

export const createTableVisCell = (formattedColumns: FormattedColumn[], rows: Table['rows']) => ({
// @ts-expect-error
colIndex,
export const createTableVisCell = (rows: DatatableRow[], formattedColumns: FormattedColumns) => ({
rowIndex,
columnId,
}: EuiDataGridCellValueElementProps) => {
const rowValue = rows[rowIndex][columnId];
const column = formattedColumns[colIndex];
const column = formattedColumns[columnId];
const content = column.formatter.convert(rowValue, 'html');

const cellContent = (
Expand Down
35 changes: 13 additions & 22 deletions src/plugins/vis_type_table/public/components/table_vis_columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ import React from 'react';
import { EuiDataGridColumnCellActionProps, EuiDataGridColumn } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import { IInterpreterRenderHandlers } from 'src/plugins/expressions';
import { Table } from '../table_vis_response_handler';
import { FormattedColumn, TableVisUiState } from '../types';
import { DatatableColumn, DatatableRow, IInterpreterRenderHandlers } from 'src/plugins/expressions';
import { FormattedColumns, TableVisUiState } from '../types';

interface FilterCellData {
/**
Expand All @@ -27,33 +26,24 @@ interface FilterCellData {
}

export const createGridColumns = (
table: Table,
columns: FormattedColumn[],
columns: DatatableColumn[],
rows: DatatableRow[],
formattedColumns: FormattedColumns,
columnsWidth: TableVisUiState['colWidth'],
rows: Table['rows'],
fireEvent: IInterpreterRenderHandlers['event']
) => {
const onFilterClick = (data: FilterCellData, negate: boolean) => {
/**
* Visible column index and the actual one from the source table could be different.
* e.x. a column could be filtered out if it's not a dimension -
* see formattedColumns in use_formatted_columns.ts file,
* or an extra percantage column could be added, which doesn't exist in the raw table
*/
const rawTableActualColumnIndex = table.columns.findIndex(
(c) => c.id === columns[data.column].id
);
fireEvent({
name: 'filterBucket',
data: {
data: [
{
table: {
...table,
columns,
rows,
},
...data,
column: rawTableActualColumnIndex,
column: data.column,
},
],
negate,
Expand All @@ -63,12 +53,13 @@ export const createGridColumns = (

return columns.map(
(col, colIndex): EuiDataGridColumn => {
const cellActions = col.filterable
const formattedColumn = formattedColumns[col.id];
const cellActions = formattedColumn.filterable
? [
({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => {
const rowValue = rows[rowIndex][columnId];
const contentsIsDefined = rowValue !== null && rowValue !== undefined;
const cellContent = col.formatter.convert(rowValue);
const cellContent = formattedColumn.formatter.convert(rowValue);

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

const filterOutText = i18n.translate(
'visTypeTable.tableCellFilter.filterOutValueText',
Expand Down Expand Up @@ -144,8 +135,8 @@ export const createGridColumns = (
const initialWidth = columnsWidth.find((c) => c.colIndex === colIndex);
const column: EuiDataGridColumn = {
id: col.id,
display: col.title,
displayAsText: col.title,
display: col.name,
displayAsText: col.name,
actions: {
showHide: false,
showMoveLeft: false,
Expand Down
148 changes: 85 additions & 63 deletions src/plugins/vis_type_table/public/components/table_vis_controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,81 +11,103 @@ import { EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } f
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';

import { DatatableRow } from 'src/plugins/expressions';
import { DatatableColumn, DatatableRow } from 'src/plugins/expressions';
import { CoreStart } from 'kibana/public';
import { useKibana } from '../../../kibana_react/public';
import { FormattedColumn } from '../types';
import { Table } from '../table_vis_response_handler';
import { exportAsCsv } from '../utils';
import { exporters } from '../../../data/public';
import {
CSV_SEPARATOR_SETTING,
CSV_QUOTE_VALUES_SETTING,
downloadFileAs,
} from '../../../share/public';
import { getFormatService } from '../services';

interface TableVisControlsProps {
dataGridAriaLabel: string;
filename?: string;
cols: FormattedColumn[];
columns: DatatableColumn[];
rows: DatatableRow[];
table: Table;
}

export const TableVisControls = memo(({ dataGridAriaLabel, ...props }: TableVisControlsProps) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const togglePopover = useCallback(() => setIsPopoverOpen((state) => !state), []);
const closePopover = useCallback(() => setIsPopoverOpen(false), []);
export const TableVisControls = memo(
({ dataGridAriaLabel, filename, columns, rows }: TableVisControlsProps) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const togglePopover = useCallback(() => setIsPopoverOpen((state) => !state), []);
const closePopover = useCallback(() => setIsPopoverOpen(false), []);

const {
services: { uiSettings },
} = useKibana<CoreStart>();
const {
services: { uiSettings },
} = useKibana<CoreStart>();

const onClickExport = useCallback(
(formatted: boolean) =>
exportAsCsv(formatted, {
...props,
uiSettings,
}),
[props, uiSettings]
);
const onClickExport = useCallback(
(formatted: boolean) => {
const csvSeparator = uiSettings.get(CSV_SEPARATOR_SETTING);
const quoteValues = uiSettings.get(CSV_QUOTE_VALUES_SETTING);

const exportBtnAriaLabel = i18n.translate('visTypeTable.vis.controls.exportButtonAriaLabel', {
defaultMessage: 'Export {dataGridAriaLabel} as CSV',
values: {
dataGridAriaLabel,
},
});
const content = exporters.datatableToCSV(
{
type: 'datatable',
columns,
rows,
},
{
csvSeparator,
quoteValues,
formatFactory: getFormatService().deserialize,
raw: !formatted,
}
);
downloadFileAs(`${filename || 'unsaved'}.csv`, { content, type: exporters.CSV_MIME_TYPE });
},
[columns, rows, filename, uiSettings]
);

const button = (
<EuiButtonEmpty
aria-label={exportBtnAriaLabel}
size="xs"
iconType="exportAction"
color="text"
className="euiDataGrid__controlBtn"
onClick={togglePopover}
>
<FormattedMessage id="visTypeTable.vis.controls.exportButtonLabel" defaultMessage="Export" />
</EuiButtonEmpty>
);
const exportBtnAriaLabel = i18n.translate('visTypeTable.vis.controls.exportButtonAriaLabel', {
defaultMessage: 'Export {dataGridAriaLabel} as CSV',
values: {
dataGridAriaLabel,
},
});

const items = [
<EuiContextMenuItem key="rawCsv" onClick={() => onClickExport(false)}>
<FormattedMessage id="visTypeTable.vis.controls.rawCSVButtonLabel" defaultMessage="Raw" />
</EuiContextMenuItem>,
<EuiContextMenuItem key="csv" onClick={() => onClickExport(true)}>
<FormattedMessage
id="visTypeTable.vis.controls.formattedCSVButtonLabel"
defaultMessage="Formatted"
/>
</EuiContextMenuItem>,
];
const button = (
<EuiButtonEmpty
aria-label={exportBtnAriaLabel}
size="xs"
iconType="exportAction"
color="text"
className="euiDataGrid__controlBtn"
onClick={togglePopover}
>
<FormattedMessage
id="visTypeTable.vis.controls.exportButtonLabel"
defaultMessage="Export"
/>
</EuiButtonEmpty>
);

return (
<EuiPopover
id="dataTableExportData"
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
repositionOnScroll
>
<EuiContextMenuPanel className="eui-textNoWrap" items={items} />
</EuiPopover>
);
});
const items = [
<EuiContextMenuItem key="rawCsv" onClick={() => onClickExport(false)}>
<FormattedMessage id="visTypeTable.vis.controls.rawCSVButtonLabel" defaultMessage="Raw" />
</EuiContextMenuItem>,
<EuiContextMenuItem key="csv" onClick={() => onClickExport(true)}>
<FormattedMessage
id="visTypeTable.vis.controls.formattedCSVButtonLabel"
defaultMessage="Formatted"
/>
</EuiContextMenuItem>,
];

return (
<EuiPopover
id="dataTableExportData"
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
repositionOnScroll
>
<EuiContextMenuPanel className="eui-textNoWrap" items={items} />
</EuiPopover>
);
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
import React, { memo } from 'react';

import { IInterpreterRenderHandlers } from 'src/plugins/expressions';
import { TableGroup } from '../table_vis_response_handler';
import { TableVisConfig, TableVisUseUiStateProps } from '../types';
import { TableGroup, TableVisConfig, TableVisUseUiStateProps } from '../types';
import { TableVisBasic } from './table_vis_basic';

interface TableVisSplitProps {
Expand All @@ -24,11 +23,11 @@ export const TableVisSplit = memo(
({ fireEvent, tables, visConfig, uiStateProps }: TableVisSplitProps) => {
return (
<>
{tables.map(({ tables: dataTable, key, title }) => (
<div key={key} className="tbvChart__split">
{tables.map(({ table, title }) => (
<div key={title} className="tbvChart__split">
<TableVisBasic
fireEvent={fireEvent}
table={dataTable[0]}
table={table}
visConfig={visConfig}
title={title}
uiStateProps={uiStateProps}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,15 @@ import { CoreStart } from 'kibana/public';
import { IInterpreterRenderHandlers } from 'src/plugins/expressions';
import type { PersistedState } from 'src/plugins/visualizations/public';
import { KibanaContextProvider } from '../../../kibana_react/public';
import { TableVisConfig } from '../types';
import { TableContext } from '../table_vis_response_handler';
import { TableVisConfig, TableVisData } from '../types';
import { TableVisBasic } from './table_vis_basic';
import { TableVisSplit } from './table_vis_split';
import { useUiState } from '../utils';

interface TableVisualizationComponentProps {
core: CoreStart;
handlers: IInterpreterRenderHandlers;
visData: TableContext;
visData: TableVisData;
visConfig: TableVisConfig;
}

Expand Down
Loading

0 comments on commit 5d1ff3e

Please sign in to comment.