diff --git a/CHANGELOG.md b/CHANGELOG.md index b44518a2a..7a578b5f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log for dash-table All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] +### Fixed +[#314](https://github.com/plotly/dash-table/issues/533) +- Fixed problem clearing one column shifting everything to the left and +leaving the last column blank +- Add merge_duplicate_headers prop to correct `export_format: display` behaviour. ## [4.1.0] - 2019-08-05 ### Added diff --git a/requirements.txt b/requirements.txt index e979bf36c..4c2caa95e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ chromedriver-binary==2.41.0 click==6.7 decorator==4.3.0 flake8==3.5.0 -Flask==1.0.2 +Flask==1.1.1 Flask-Compress==1.4.0 gunicorn==19.9.0 idna==2.7 diff --git a/src/dash-table/components/ControlledTable/index.tsx b/src/dash-table/components/ControlledTable/index.tsx index 7d37039bb..531049bb1 100644 --- a/src/dash-table/components/ControlledTable/index.tsx +++ b/src/dash-table/components/ControlledTable/index.tsx @@ -786,13 +786,14 @@ export default class ControlledTable extends PureComponent tooltip_duration ); - const { export_format, export_headers, virtual } = this.props; + const { export_format, export_headers, virtual, merge_duplicate_headers } = this.props; const buttonProps = { export_format, virtual_data: virtual, columns, visibleColumns, - export_headers + export_headers, + merge_duplicate_headers }; return (
{ - const { columns, export_format, virtual_data, export_headers, visibleColumns } = props; + const { columns, export_format, virtual_data, export_headers, visibleColumns, merge_duplicate_headers } = props; const isFormatSupported = export_format === 'csv' || export_format === 'xlsx'; const handleExport = () => { @@ -22,7 +23,7 @@ export default React.memo((props: IExportButtonProps) => { const columnHeaders = visibleColumns.map(column => column.name); const maxLength = getHeaderRows(columns); const heading = (export_headers !== 'none') ? createHeadings(columnHeaders, maxLength) : []; - const ws = createWorksheet(heading, virtual_data.data, columnID, export_headers); + const ws = createWorksheet(heading, virtual_data.data, columnID, export_headers, merge_duplicate_headers); const wb = createWorkbook(ws); if (export_format === 'xlsx') { XLSX.writeFile(wb, 'Data.xlsx', {bookType: 'xlsx', type: 'buffer'}); diff --git a/src/dash-table/components/Export/utils.tsx b/src/dash-table/components/Export/utils.tsx index 19d6f225e..b322f97df 100644 --- a/src/dash-table/components/Export/utils.tsx +++ b/src/dash-table/components/Export/utils.tsx @@ -53,14 +53,15 @@ export function createWorkbook(ws: XLSX.WorkSheet) { return wb; } -export function createWorksheet(heading: string[][], data: Data, columnID: string[], exportHeader: string ) { +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') { + if (exportHeader === 'display' && mergeDuplicateHeaders) { ws['!merges'] = getMergeRanges(heading); } } else if (exportHeader === 'ids') { diff --git a/tests/cypress/tests/unit/exportUtils_tests.ts b/tests/cypress/tests/unit/exportUtils_tests.ts index 6a3c4cc24..e635cfb19 100644 --- a/tests/cypress/tests/unit/exportUtils_tests.ts +++ b/tests/cypress/tests/unit/exportUtils_tests.ts @@ -218,11 +218,12 @@ describe('export', () => { {col1: 1, col2: 2, col3: 3}, {col1: 2, col2: 3, col3: 4}, {col1: 1, col2: 2, col3: 3} - ]; + ]; + const columnID = ['col1', 'col2', 'col3']; it('create sheet with column names as headers for name or display header mode', () => { - const columnID = ['col1', 'col2', 'col3']; - const wsName = createWorksheet(Headings, data, columnID, 'names'); - const wsDisplay = createWorksheet(Headings, data, columnID, 'display'); + 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 expectedWS = { A1: {t: 's', v: 'rows'}, A2: {t: 's', v: 'rows'}, @@ -251,11 +252,11 @@ describe('export', () => { {s: {r: 1, c: 1}, e: {r: 1, c: 2}}, {s: {r: 3, c: 0}, e: {r: 3, c: 2}} ]; expect(wsName).to.deep.equal(expectedWS); + expect(wsDisplayNoMerge).to.deep.equal(expectedWS); expect(wsDisplay).to.deep.equal(expectedWSDisplay); }); it('create sheet with column ids as headers', () => { - const columnID = ['col1', 'col2', 'col3']; - const ws = createWorksheet(Headings, data, columnID, 'ids'); + const ws = createWorksheet(Headings, data, columnID, 'ids', true); const expectedWS = { A1: {t: 's', v: 'col1'}, A2: {t: 'n', v: 1}, @@ -273,8 +274,7 @@ describe('export', () => { expect(ws).to.deep.equal(expectedWS); }); it('create sheet with no headers', () => { - const columnID = ['col1', 'col2', 'col3']; - const ws = createWorksheet([], data, columnID, 'none'); + const ws = createWorksheet([], data, columnID, 'none', true); const expectedWS = { A1: {t: 'n', v: 1}, A2: {t: 'n', v: 2}, @@ -288,6 +288,34 @@ describe('export', () => { expectedWS['!ref'] = 'A1:C3'; expect(ws).to.deep.equal(expectedWS); }); + it('create sheet with undefined column for clearable columns', () => { + const newData = [ + {col2: 2, col3: 3}, + {col2: 3, col3: 4}, + {col2: 2, col3: 3} + ]; + const ws = createWorksheet(Headings, newData, columnID, 'display', false); + const expectedWS = {A1: {t: 's', v: 'rows'}, + A2: {t: 's', v: 'rows'}, + A3: {t: 's', v: 'rows'}, + A4: {t: 's', v: 'rows'}, + B1: {t: 's', v: 'rows'}, + B2: {t: 's', v: 'c'}, + B3: {t: 's', v: 'e'}, + B4: {t: 's', v: 'rows'}, + B5: {t: 'n', v: 2}, + B6: {t: 'n', v: 3}, + B7: {t: 'n', v: 2}, + C1: {t: 's', v: 'b'}, + C2: {t: 's', v: 'c'}, + C3: {t: 's', v: 'f'}, + C4: {t: 's', v: 'rows'}, + C5: {t: 'n', v: 3}, + C6: {t: 'n', v: 4}, + C7: {t: 'n', v: 3}}; + expectedWS['!ref'] = 'A1:C7'; + expect(ws).to.deep.equal(expectedWS); + }); }); });