From eb6cc867a89eedfbca0633441263fbed5dfeefbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8C=80=EC=97=B0?= Date: Fri, 5 Nov 2021 17:23:22 +0900 Subject: [PATCH] fix: export with formatted values (#1499) * test: add test for export * fix: export with formatted values * chore: apply code reviews * chore: fix lint error * chore: remove unnecessary only flag in test * chore: remove 'ToUse' suffix --- .../cypress/integration/data.spec.ts | 2 +- .../cypress/integration/export.spec.ts | 47 ++++++++++++- packages/toast-ui.grid/docs/ko/export.md | 1 + packages/toast-ui.grid/src/dispatch/export.ts | 4 +- packages/toast-ui.grid/src/grid.tsx | 1 + packages/toast-ui.grid/src/query/export.ts | 70 +++++++++++++------ .../toast-ui.grid/types/store/export.d.ts | 1 + 7 files changed, 98 insertions(+), 28 deletions(-) diff --git a/packages/toast-ui.grid/cypress/integration/data.spec.ts b/packages/toast-ui.grid/cypress/integration/data.spec.ts index a8409d469..a8d8ebca5 100644 --- a/packages/toast-ui.grid/cypress/integration/data.spec.ts +++ b/packages/toast-ui.grid/cypress/integration/data.spec.ts @@ -723,7 +723,7 @@ describe('setRow()', () => { cy.getCell(1, 'name').should('have.text', 'observable'); }); - it.only('should destroy the focusing layer, only when row will be filtered', () => { + it('should destroy the focusing layer, only when row will be filtered', () => { createGrid(); cy.gridInstance().invoke('setFilter', 'age', 'number'); invokeFilter('age', [{ code: 'gt', value: 5 }]); diff --git a/packages/toast-ui.grid/cypress/integration/export.spec.ts b/packages/toast-ui.grid/cypress/integration/export.spec.ts index 38cea1c9f..8618a2146 100644 --- a/packages/toast-ui.grid/cypress/integration/export.spec.ts +++ b/packages/toast-ui.grid/cypress/integration/export.spec.ts @@ -26,9 +26,9 @@ function clickExportMenuItemByFormat(format: 'csv' | 'xlsx') { describe('Export data', () => { const data = [ - { name: 'Sugar', artist: 'Maroon5', price: 1000 }, - { name: 'Get Lucky', artist: 'Daft Punk', price: 2000 }, - { name: '21', artist: 'Adele', price: 3000 }, + { name: 'Sugar', artist: 'Maroon5', price: 1000, typeCode: '2' }, + { name: 'Get Lucky', artist: 'Daft Punk', price: 2000, typeCode: '3' }, + { name: '21', artist: 'Adele', price: 3000, typeCode: '1' }, ]; const columns = [ { @@ -308,4 +308,45 @@ describe('Export data', () => { }); }); }); + + describe('useFormattedValue option', () => { + beforeEach(() => { + const formatColumn = [ + { + header: 'Type', + name: 'typeCode', + formatter: 'listItemText', + editor: { + type: 'select', + options: { + listItems: [ + { text: 'Deluxe', value: '1' }, + { text: 'EP', value: '2' }, + { text: 'Single', value: '3' }, + ], + }, + }, + }, + ]; + cy.gridInstance().invoke('setColumns', formatColumn); + }); + + ['csv', 'xlsx'].forEach((format) => { + it(`should export formatted data to '${format}' (useFormattedValue = true)`, () => { + cy.gridInstance().invoke('export', format, { useFormattedValue: true }); + + cy.wrap(callback).should('be.calledWithMatch', { + data: [['Type'], ['EP'], ['Single'], ['Deluxe']], + }); + }); + + it(`should export original data to '${format}' (useFormattedValue = false(default))`, () => { + cy.gridInstance().invoke('export', format); + + cy.wrap(callback).should('be.calledWithMatch', { + data: [['Type'], ['2'], ['3'], ['1']], + }); + }); + }); + }); }); diff --git a/packages/toast-ui.grid/docs/ko/export.md b/packages/toast-ui.grid/docs/ko/export.md index 8416e9890..572b07dcd 100644 --- a/packages/toast-ui.grid/docs/ko/export.md +++ b/packages/toast-ui.grid/docs/ko/export.md @@ -13,6 +13,7 @@ TOAST UI Grid는 `v4.19.0` 버전 부터 `csv`와 엑셀(`xlsx`)로 내보내기 | `columnNames` | `string[]` | `[...보이는 모든 컬럼명]` | 내보내려는 컬럼을 선택한다.
해당 배열에 요소가 1개 이상 전달되면 `includeHiddenColumns` 옵션의 값과 상관 없이 전달 받은 컬럼을 내보낸다. | | `onlySelected` | `boolean` | `false` | 선택한 영역만 내보낼지 여부를 결정한다.
값이 참이면 `includeHiddenColumns` 옵션과 `columnNames` 옵션의 값과 상관 없이 현재 선택한 영역만 내보낸다.
선택 영역이 없다면 옵션 값과 상관 없이 지정한 컬럼의 데이터 또는 숨겨진 컬럼을 포함한 모든 컬럼의 데이터 또는 보이는 컬럼의 데이터를 내보낸다. | | `onlyFiltered` | `boolean` | `true` | 필터링된 데이터만 내보낼지 여부를 결정한다.
값이 참이면 필터링된 데이터만 내보내고, 거짓이면 모든 데이터를 내보낸다. | +| `useFormattedValue` | `boolean` | `false` | 포맷된 데이터를 내보낼지 여부를 결정한다.
값이 참이면 포맷된 데이터를 내보내고, 거짓이면 원본 데이터를 내보낸다. | | `delimiter` | `','\|';'\|'\t'\|'\|'` | `','` | CSV 내보내기 시 구분자를 정의한다. | | `fileName` | `string` | `'grid-export'` | 내보낼 파일의 이름을 정의한다. | diff --git a/packages/toast-ui.grid/src/dispatch/export.ts b/packages/toast-ui.grid/src/dispatch/export.ts index f2c72f269..4c58db476 100644 --- a/packages/toast-ui.grid/src/dispatch/export.ts +++ b/packages/toast-ui.grid/src/dispatch/export.ts @@ -34,6 +34,7 @@ function getExportDataAndColumnsAndOptions(store: Store, options?: OptExport) { onlyFiltered = true, delimiter = ',', fileName = 'grid-export', + useFormattedValue = false, } = options || {}; const { @@ -57,7 +58,8 @@ function getExportDataAndColumnsAndOptions(store: Store, options?: OptExport) { store, onlyFiltered ? filteredRawData : rawData, columnNames, - onlySelected + onlySelected, + useFormattedValue ); const exportOptions = { diff --git a/packages/toast-ui.grid/src/grid.tsx b/packages/toast-ui.grid/src/grid.tsx index 91357a517..fbe7e3af3 100644 --- a/packages/toast-ui.grid/src/grid.tsx +++ b/packages/toast-ui.grid/src/grid.tsx @@ -1720,6 +1720,7 @@ export default class Grid implements TuiGrid { * @param {string[]} [options.columnNames=[...allVisibleColumnNames]] - Columns names to export * @param {boolean} [options.onlySelected=false] - Whether to export only the selected range * @param {boolean} [options.onlyFiltered=true] - Whether to export only the filtered data + * @param {boolean} [options.useFormattedValue=false] - Whether to export formatted values or original values * @param {','|';'|'\\t'|'|'} [options.delimiter=','] - Delimiter to export CSV * @param {string} [options.fileName='grid-export'] - File name to export */ diff --git a/packages/toast-ui.grid/src/query/export.ts b/packages/toast-ui.grid/src/query/export.ts index cc8c0bcaf..5f72e6c8a 100644 --- a/packages/toast-ui.grid/src/query/export.ts +++ b/packages/toast-ui.grid/src/query/export.ts @@ -8,6 +8,8 @@ import { isCheckboxColumn, isDragColumn, isRowNumColumn } from '../helper/column import { includes } from '../helper/common'; import GridEvent from '../event/gridEvent'; import { convertHierarchyToData, getComplexColumnsHierarchy } from './column'; +import { createFormattedValue } from '../store/helper/data'; +import { Dictionary } from '@t/options'; export type EventType = 'beforeExport' | 'afterExport'; @@ -18,6 +20,37 @@ export interface EventParams { exportFn?: (data: string[][]) => void; } +function getColumnInfoDictionary(store: Store, columnNames: string[]) { + const colmnInfos: Dictionary = {}; + + store.column.allColumns.forEach((columnInfo) => { + if (includes(columnNames, columnInfo.name)) { + colmnInfos[columnInfo.name] = columnInfo; + } + }); + + return colmnInfos; +} + +function getValue( + row: Row, + colmnInfos: Dictionary, + columName: string, + useFormattedValue: boolean, + index?: number +) { + if (isRowNumColumn(columName)) { + return `No.${index! + 1}`; + } + + const origianlValue = row[columName]; + const formattedValue = createFormattedValue(row, colmnInfos[columName]); + + return useFormattedValue && String(origianlValue) !== formattedValue + ? formattedValue + : (origianlValue as string); +} + export function createExportEvent(eventType: EventType, eventParams: EventParams) { const { exportFormat, exportOptions, data, exportFn } = eventParams; let props: GridEventProps = {}; @@ -129,33 +162,24 @@ export function getTargetData( store: Store, rows: Row[], columnNames: string[], - onlySelected: boolean + onlySelected: boolean, + useFormattedValue: boolean ) { - if (onlySelected) { - let targetRows = rows; + const colmnInfoDictionary = getColumnInfoDictionary(store, columnNames); + const { + selection: { originalRange }, + } = store; - const { - selection: { originalRange }, - } = store; + let targetRows = rows; - if (originalRange) { - const [rowStart, rowEnd] = originalRange.row; - targetRows = rows.slice(rowStart, rowEnd + 1); - } - - return targetRows.map((row) => columnNames.map((colName) => row[colName] as string)); + if (onlySelected && originalRange) { + const [rowStart, rowEnd] = originalRange.row; + targetRows = rows.slice(rowStart, rowEnd + 1); } - const data = rows.map((row, index) => - columnNames.reduce((rowData: string[], colName) => { - if (isRowNumColumn(colName)) { - rowData.push(`No.${index + 1}`); - } else { - rowData.push(row[colName] as string); - } - return rowData; - }, []) + return targetRows.map((row, index) => + columnNames.map((colName) => + getValue(row, colmnInfoDictionary, colName, useFormattedValue, index) + ) ); - - return data; } diff --git a/packages/toast-ui.grid/types/store/export.d.ts b/packages/toast-ui.grid/types/store/export.d.ts index b40c2491e..ff625d1b5 100644 --- a/packages/toast-ui.grid/types/store/export.d.ts +++ b/packages/toast-ui.grid/types/store/export.d.ts @@ -6,4 +6,5 @@ export interface OptExport { columnNames?: string[]; delimiter?: ',' | ';' | '\t' | '|'; fileName?: string; + useFormattedValue?: boolean; }