-
-
Notifications
You must be signed in to change notification settings - Fork 73
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; | ||
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[][] { | ||
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 } | ||
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'}, | ||
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} | ||
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