-
-
Notifications
You must be signed in to change notification settings - Fork 72
Issue 546 - Visible columns and data misalignment in export #592
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,33 +1,37 @@ | ||
| import XLSX from 'xlsx'; | ||
| import React from 'react'; | ||
| import { IDerivedData, Columns } from 'dash-table/components/Table/props'; | ||
| import { IDerivedData, Columns, ExportHeaders, ExportFormat, ExportColumns } from 'dash-table/components/Table/props'; | ||
| import { createWorkbook, createHeadings, createWorksheet } from './utils'; | ||
| import getHeaderRows from 'dash-table/derived/header/headerRows'; | ||
|
|
||
| interface IExportButtonProps { | ||
| columns: Columns; | ||
| export_format: string; | ||
| export_columns: ExportColumns; | ||
| export_format: ExportFormat; | ||
| virtual_data: IDerivedData; | ||
| visibleColumns: Columns; | ||
| export_headers: string; | ||
| export_headers: ExportHeaders; | ||
| merge_duplicate_headers: boolean; | ||
| } | ||
|
|
||
| export default React.memo((props: IExportButtonProps) => { | ||
|
|
||
| const { columns, export_format, virtual_data, export_headers, visibleColumns, merge_duplicate_headers } = props; | ||
| const isFormatSupported = export_format === 'csv' || export_format === 'xlsx'; | ||
| const { columns, export_columns, export_format, virtual_data, export_headers, visibleColumns, merge_duplicate_headers } = props; | ||
| const isFormatSupported = export_format === ExportFormat.Csv || export_format === ExportFormat.Xlsx; | ||
|
|
||
| const exportedColumns = export_columns === ExportColumns.Visible ? visibleColumns : columns; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use either visible columns or all columns depending on the prop setting |
||
|
|
||
| const handleExport = () => { | ||
| const columnID = visibleColumns.map(column => column.id); | ||
| const columnHeaders = visibleColumns.map(column => column.name); | ||
| const columnID = exportedColumns.map(column => column.id); | ||
| const columnHeaders = exportedColumns.map(column => column.name); | ||
|
|
||
| const maxLength = getHeaderRows(columns); | ||
| const heading = (export_headers !== 'none') ? createHeadings(columnHeaders, maxLength) : []; | ||
| const heading = (export_headers !== ExportHeaders.None) ? createHeadings(columnHeaders, maxLength) : []; | ||
| const ws = createWorksheet(heading, virtual_data.data, columnID, export_headers, merge_duplicate_headers); | ||
| const wb = createWorkbook(ws); | ||
| if (export_format === 'xlsx') { | ||
| if (export_format === ExportFormat.Xlsx) { | ||
| XLSX.writeFile(wb, 'Data.xlsx', {bookType: 'xlsx', type: 'buffer'}); | ||
| } else if (export_format === 'csv') { | ||
| } else if (export_format === ExportFormat.Csv) { | ||
| XLSX.writeFile(wb, 'Data.csv', {bookType: 'csv', type: 'buffer'}); | ||
| } | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,13 @@ | ||
| import * as R from 'ramda'; | ||
| import XLSX from 'xlsx'; | ||
| import { Data } from 'dash-table/components/Table/props'; | ||
| import { Data, ExportHeaders } from 'dash-table/components/Table/props'; | ||
|
|
||
| interface IMergeObject { | ||
| s: {r: number, c: number}; | ||
| e: {r: number, c: number}; | ||
| } | ||
|
|
||
| export function transformMultDimArray(array: (string | string[])[], maxLength: number): string[][] { | ||
| export function transformMultiDimArray(array: (string | string[])[], maxLength: number): string[][] { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo |
||
| const newArray: string[][] = array.map(row => { | ||
| if (row instanceof Array && row.length < maxLength) { | ||
| return row.concat(Array(maxLength - row.length).fill('')); | ||
|
|
@@ -53,24 +53,30 @@ export function createWorkbook(ws: XLSX.WorkSheet) { | |
| return wb; | ||
| } | ||
|
|
||
| export function createWorksheet(heading: string[][], data: Data, columnID: string[], exportHeader: string, mergeDuplicateHeaders: boolean ) { | ||
| const ws = XLSX.utils.aoa_to_sheet(heading); | ||
| if (exportHeader === 'display' || exportHeader === 'names' || exportHeader === 'none') { | ||
| XLSX.utils.sheet_add_json(ws, data, { | ||
| header: columnID, | ||
| skipHeader: true, | ||
| origin: heading.length | ||
| }); | ||
| if (exportHeader === 'display' && mergeDuplicateHeaders) { | ||
| export function createWorksheet(heading: string[][], data: Data, columnID: string[], exportHeader: ExportHeaders, mergeDuplicateHeaders: boolean) { | ||
| const ws = XLSX.utils.aoa_to_sheet([]); | ||
|
|
||
| data = R.map(R.pick(columnID))(data); | ||
|
|
||
| if (exportHeader === ExportHeaders.Display || exportHeader === ExportHeaders.Names || exportHeader === ExportHeaders.None) { | ||
| XLSX.utils.sheet_add_json(ws, heading, { skipHeader: true }); | ||
|
|
||
| const contentOptions = heading.length > 0 ? | ||
| { header: columnID, skipHeader: true, origin: heading.length } : | ||
| { skipHeader: true }; | ||
|
|
||
| XLSX.utils.sheet_add_json(ws, data, contentOptions); | ||
|
|
||
| if (exportHeader === ExportHeaders.Display && mergeDuplicateHeaders) { | ||
| ws['!merges'] = getMergeRanges(heading); | ||
| } | ||
| } else if (exportHeader === 'ids') { | ||
| } else if (exportHeader === ExportHeaders.Ids) { | ||
| XLSX.utils.sheet_add_json(ws, data, { header: columnID }); | ||
| } | ||
| return ws; | ||
| } | ||
|
|
||
| export function createHeadings(columnHeaders: (string | string[])[], maxLength: number) { | ||
| const transformedArray = transformMultDimArray(columnHeaders, maxLength); | ||
| const transformedArray = transformMultiDimArray(columnHeaders, maxLength); | ||
| return R.transpose(transformedArray); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,44 +1,45 @@ | ||
| import { transformMultDimArray, getMergeRanges, createHeadings, createWorksheet } from 'dash-table/components/Export/utils'; | ||
| import { transformMultiDimArray, getMergeRanges, createHeadings, createWorksheet } from 'dash-table/components/Export/utils'; | ||
| import * as R from 'ramda'; | ||
| import { ExportHeaders } from 'dash-table/components/Table/props'; | ||
|
|
||
| describe('export', () => { | ||
|
|
||
| describe('transformMultDimArray', () => { | ||
| describe('transformMultiDimArray', () => { | ||
| it('array with only strings', () => { | ||
| const testedArray = []; | ||
| const transformedArray = transformMultDimArray(testedArray, 0); | ||
| const transformedArray = transformMultiDimArray(testedArray, 0); | ||
| const expectedArray = []; | ||
| expect(transformedArray).to.deep.equal(expectedArray); | ||
| }); | ||
| it('array with only strings', () => { | ||
| const testedArray = ['a', 'b', 'c', 'd']; | ||
| const transformedArray = transformMultDimArray(testedArray, 0); | ||
| const transformedArray = transformMultiDimArray(testedArray, 0); | ||
| const expectedArray = [['a'], ['b'], ['c'], ['d']]; | ||
| expect(transformedArray).to.deep.equal(expectedArray); | ||
| }); | ||
| it ('array with strings and strings array with same length', () => { | ||
| const testedArray = ['a', ['b', 'c'], ['b', 'd']]; | ||
| const transformedArray = transformMultDimArray(testedArray, 2); | ||
| const transformedArray = transformMultiDimArray(testedArray, 2); | ||
| const expectedArray = [['a', 'a'], ['b', 'c'], ['b', 'd']]; | ||
| expect(transformedArray).to.deep.equal(expectedArray); | ||
| }); | ||
| it ('2D strings array', () => { | ||
| const testedArray = [['a', 'b', 'c'], ['b', 'c', 'd'], ['b', 'd', 'a']]; | ||
| const transformedArray = transformMultDimArray(testedArray, 3); | ||
| const transformedArray = transformMultiDimArray(testedArray, 3); | ||
| const expectedArray = [['a', 'b', 'c'], ['b', 'c', 'd'], ['b', 'd', 'a']]; | ||
| expect(transformedArray).to.deep.equal(expectedArray); | ||
|
|
||
| }); | ||
| it ('multidimensional array', () => { | ||
| const testedArray = [['a', 'b'], ['b', 'c', 'd'], ['a', 'b', 'd', 'a']]; | ||
| const transformedArray = transformMultDimArray(testedArray, 4); | ||
| const transformedArray = transformMultiDimArray(testedArray, 4); | ||
| const expectedArray = [['a', 'b', '', ''], ['b', 'c', 'd', ''], ['a', 'b', 'd', 'a']]; | ||
| expect(transformedArray).to.deep.equal(expectedArray); | ||
|
|
||
| }); | ||
| it ('multidimensional array with strings', () => { | ||
| const testedArray = ['rows', ['a', 'b'], ['b', 'c', 'd'], ['a', 'b', 'd', 'a']]; | ||
| const transformedArray = transformMultDimArray(testedArray, 4); | ||
| const transformedArray = transformMultiDimArray(testedArray, 4); | ||
| const expectedArray = [['rows', 'rows', 'rows', 'rows'], ['a', 'b', '', ''], ['b', 'c', 'd', ''], ['a', 'b', 'd', 'a']]; | ||
| expect(transformedArray).to.deep.equal(expectedArray); | ||
| }); | ||
|
|
@@ -210,20 +211,24 @@ describe('export', () => { | |
| }); | ||
|
|
||
| describe('createWorksheet ', () => { | ||
| const Headings = [['rows', 'rows', 'b'], | ||
| ['rows', 'c', 'c'], | ||
| ['rows', 'e', 'f'], | ||
| ['rows', 'rows', 'rows']]; | ||
| const Headings = [ | ||
| ['rows', 'rows', 'b'], | ||
| ['rows', 'c', 'c'], | ||
| ['rows', 'e', 'f'], | ||
| ['rows', 'rows', 'rows'] | ||
| ]; | ||
|
|
||
| const data = [ | ||
| {col1: 1, col2: 2, col3: 3}, | ||
| {col1: 2, col2: 3, col3: 4}, | ||
| {col1: 1, col2: 2, col3: 3} | ||
| { col1: 1, col2: 2, col3: 'x', col4: 3 }, | ||
| { col1: 2, col2: 3, col3: 'x', col4: 4 }, | ||
| { col1: 1, col2: 2, col3: 'x', col4: 3 } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add one unused (hidden) column to the dataframe |
||
| ]; | ||
| const columnID = ['col1', 'col2', 'col3']; | ||
|
|
||
| const columnID = ['col1', 'col2', 'col4']; | ||
| it('create sheet with column names as headers for name or display header mode', () => { | ||
| const wsName = createWorksheet(Headings, data, columnID, 'names', true); | ||
| const wsDisplay = createWorksheet(Headings, data, columnID, 'display', true); | ||
| const wsDisplayNoMerge = createWorksheet(Headings, data, columnID, 'display', false); | ||
| const wsName = createWorksheet(Headings, data, columnID, ExportHeaders.Names, true); | ||
| const wsDisplay = createWorksheet(Headings, data, columnID, ExportHeaders.Display, true); | ||
| const wsDisplayNoMerge = createWorksheet(Headings, data, columnID, ExportHeaders.Display, false); | ||
| const expectedWS = { | ||
| A1: {t: 's', v: 'rows'}, | ||
| A2: {t: 's', v: 'rows'}, | ||
|
|
@@ -256,7 +261,7 @@ describe('export', () => { | |
| expect(wsDisplay).to.deep.equal(expectedWSDisplay); | ||
| }); | ||
| it('create sheet with column ids as headers', () => { | ||
| const ws = createWorksheet(Headings, data, columnID, 'ids', true); | ||
| const ws = createWorksheet(Headings, data, columnID, ExportHeaders.Ids, true); | ||
| const expectedWS = { | ||
| A1: {t: 's', v: 'col1'}, | ||
| A2: {t: 'n', v: 1}, | ||
|
|
@@ -266,15 +271,15 @@ describe('export', () => { | |
| B2: {t: 'n', v: 2}, | ||
| B3: {t: 'n', v: 3}, | ||
| B4: {t: 'n', v: 2}, | ||
| C1: {t: 's', v: 'col3'}, | ||
| C1: {t: 's', v: 'col4'}, | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed col3 for col4 above |
||
| C2: {t: 'n', v: 3}, | ||
| C3: {t: 'n', v: 4}, | ||
| C4: {t: 'n', v: 3}}; | ||
| expectedWS['!ref'] = 'A1:C4'; | ||
| expect(ws).to.deep.equal(expectedWS); | ||
| }); | ||
| it('create sheet with no headers', () => { | ||
| const ws = createWorksheet([], data, columnID, 'none', true); | ||
| const ws = createWorksheet([], data, columnID, ExportHeaders.None, true); | ||
| const expectedWS = { | ||
| A1: {t: 'n', v: 1}, | ||
| A2: {t: 'n', v: 2}, | ||
|
|
@@ -290,11 +295,11 @@ describe('export', () => { | |
| }); | ||
| it('create sheet with undefined column for clearable columns', () => { | ||
| const newData = [ | ||
| {col2: 2, col3: 3}, | ||
| {col2: 3, col3: 4}, | ||
| {col2: 2, col3: 3} | ||
| {col2: 2, col4: 3}, | ||
| {col2: 3, col4: 4}, | ||
| {col2: 2, col4: 3} | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. col3 became col4 above |
||
| ]; | ||
| const ws = createWorksheet(Headings, newData, columnID, 'display', false); | ||
| const ws = createWorksheet(Headings, newData, columnID, ExportHeaders.Display, false); | ||
| const expectedWS = {A1: {t: 's', v: 'rows'}, | ||
| A2: {t: 's', v: 'rows'}, | ||
| A3: {t: 's', v: 'rows'}, | ||
|
|
||
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.
Updated string usage to enums