Skip to content

Commit

Permalink
feat(export): add autoDetectCellFormat flag to Excel Export Options (
Browse files Browse the repository at this point in the history
…#1083)

- prior to this PR, the auto-detect was always enabled especially when column has a number field type but sometime the auto-detection is wrong and we wish to disable it, so this PR is adding a flag that allow the user to disable the auto-detect cell format, it can be provided from either the grid options or from a column definition, but in both cases it has to be provided into the `excelExportOptions: { autoDetectCellFormat: false }`
  • Loading branch information
ghiscoding committed Aug 18, 2023
1 parent 2dac78a commit 839b09a
Show file tree
Hide file tree
Showing 8 changed files with 57 additions and 10 deletions.
Expand Up @@ -58,6 +58,7 @@ export default class Example6 {
{
id: 'size', name: 'Size', field: 'size', minWidth: 90,
type: FieldType.number, exportWithFormatter: true,
excelExportOptions: { autoDetectCellFormat: false },
filterable: true, filter: { model: Filters.compoundInputNumber },
formatter: (_row, _cell, value) => isNaN(value) ? '' : `${value} MB`,
},
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/global-grid-options.ts
Expand Up @@ -150,6 +150,7 @@ export const GlobalGridOptions: GridOption = {
explicitInitialization: true,
excelExportOptions: {
addGroupIndentation: true,
autoDetectCellFormat: true,
exportWithFormatter: false,
filename: 'export',
format: FileType.xlsx,
Expand Down
Expand Up @@ -4,9 +4,12 @@ import type { GridOption } from './gridOption.interface';

/** Excel custom export options (formatting & width) that can be applied to a column */
export interface ColumnExcelExportOption {
/** Defaults to true, when enabled the system will try to find the best possible format to use when exporting. */
autoDetectCellFormat?: boolean;

/**
* Option to provide custom Excel styling
* NOTE: this option will completely override any detected column formatting
* NOTE: this option will completely override any detected cell styling
*/
style?: ExcelCustomStyling;

Expand All @@ -20,7 +23,7 @@ export interface ColumnExcelExportOption {
export interface GroupTotalExportOption {
/**
* Option to provide custom Excel styling
* NOTE: this option will completely override any detected column formatting
* NOTE: this option will completely override any detected cell styling
*/
style?: ExcelCustomStyling;

Expand Down
3 changes: 3 additions & 0 deletions packages/common/src/interfaces/excelExportOption.interface.ts
Expand Up @@ -7,6 +7,9 @@ export interface ExcelExportOption {
/** Defaults to true, when grid is using Grouping, it will show indentation of the text with collapsed/expanded symbol as well */
addGroupIndentation?: boolean;

/** Defaults to true, when enabled the system will try to find the best possible format to use when exporting */
autoDetectCellFormat?: boolean;

/** When defined, this will override header titles styling, when undefined the default will be a bold style */
columnHeaderStyle?: ExcelCustomStyling;

Expand Down
41 changes: 39 additions & 2 deletions packages/excel-export/src/excelExport.service.spec.ts
Expand Up @@ -20,7 +20,7 @@ import * as ExcelBuilder from 'excel-builder-webpacker';
import { ContainerServiceStub } from '../../../test/containerServiceStub';
import { TranslateServiceStub } from '../../../test/translateServiceStub';
import { ExcelExportService } from './excelExport.service';
import { getExcelSameInputDataCallback, useCellFormatByFieldType } from './excelUtils';
import { getExcelNumberCallback, getExcelSameInputDataCallback, useCellFormatByFieldType } from './excelUtils';

const pubSubServiceStub = {
publish: jest.fn(),
Expand Down Expand Up @@ -1034,7 +1034,7 @@ describe('ExcelExportService', () => {
beforeEach(() => {
mockGridOptions.enableGrouping = true;
mockGridOptions.enableTranslate = true;
mockGridOptions.excelExportOptions = { sanitizeDataExport: true, addGroupIndentation: true, exportWithFormatter: true };
mockGridOptions.excelExportOptions = { autoDetectCellFormat: true, sanitizeDataExport: true, addGroupIndentation: true, exportWithFormatter: true };
mockColumns = [
{ id: 'id', field: 'id', excludeFromExport: true },
{ id: 'userId', field: 'userId', name: 'User Id', width: 100 },
Expand Down Expand Up @@ -1125,6 +1125,7 @@ describe('ExcelExportService', () => {
service.init(gridStub, container);
await service.exportToExcel(mockExportExcelOptions);

expect(groupTotalParserCallbackSpy).toHaveBeenCalled();
expect(pubSubSpy).toHaveBeenCalledWith(`onAfterExportToExcel`, optionExpectation);
expect(spyUrlCreate).toHaveBeenCalledWith(mockExcelBlob);
expect(spyDownload).toHaveBeenCalledWith({
Expand All @@ -1149,6 +1150,22 @@ describe('ExcelExportService', () => {
});
});

it(`should not call group total value parser when column "exportAutoDetectCellFormat" is disabled`, async () => {
const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish');
const spyUrlCreate = jest.spyOn(URL, 'createObjectURL');
const spyDownload = jest.spyOn(service, 'startDownloadFile');
groupTotalParserCallbackSpy.mockReturnValue(9999);

mockGridOptions.excelExportOptions!.autoDetectCellFormat = false;
const optionExpectation = { filename: 'export.xlsx', format: 'xlsx' };

service.init(gridStub, container);
await service.exportToExcel(mockExportExcelOptions);

expect(groupTotalParserCallbackSpy).not.toHaveBeenCalled();
expect(pubSubSpy).toHaveBeenCalledWith(`onAfterExportToExcel`, optionExpectation);
});

it(`should have a xlsx export with grouping but without indentation when "addGroupIndentation" is set to False
and field should be exported as metadata when "exportWithFormatter" is false and the field type is number`, async () => {
mockColumns[5].exportWithFormatter = false; // "order" field that is of type number will be exported as a number cell format metadata
Expand Down Expand Up @@ -1198,6 +1215,26 @@ describe('ExcelExportService', () => {

expect(output).toEqual({ getDataValueParser: expect.toBeFunction(), stylesheetFormatterId: undefined });
});

it('should return a number format when using FieldType.number and a number is provided as input', async () => {
const column = { type: FieldType.number } as Column;

service.init(gridStub, container);
await service.exportToExcel(mockExportExcelOptions);
const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, column, gridStub);

expect(output).toEqual({ getDataValueParser: expect.toBeFunction(), stylesheetFormatterId: 3 });
});

it('should NOT return a number format when using FieldType.number but autoDetectCellFormat is disabled', async () => {
const column = { type: FieldType.number, excelExportOptions: { autoDetectCellFormat: false } } as Column;

service.init(gridStub, container);
await service.exportToExcel(mockExportExcelOptions);
const output = useCellFormatByFieldType(service.stylesheet, service.stylesheetFormats, column, gridStub, false);

expect(output).toEqual({ getDataValueParser: expect.toBeFunction(), stylesheetFormatterId: undefined });
});
});

describe('Grouped Column Header Titles', () => {
Expand Down
8 changes: 5 additions & 3 deletions packages/excel-export/src/excelExport.service.ts
Expand Up @@ -566,15 +566,16 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ

// for column that are Date type, we'll always export with their associated Date Formatters unless `exportWithFormatter` is specifically set to false
const exportOptions = { ...this._excelExportOptions };
if (columnDef?.exportWithFormatter !== false && isColumnDateType(fieldType)) {
if (columnDef.exportWithFormatter !== false && isColumnDateType(fieldType)) {
exportOptions.exportWithFormatter = true;
}
itemData = exportWithFormatterWhenDefined(row, col, columnDef, itemObj, this._grid, exportOptions);

// auto-detect best possible Excel format, unless the user provide his own formatting,
// we only do this check once per column (everything after that will be pull from temp ref)
if (!this._regularCellExcelFormats.hasOwnProperty(columnDef.id)) {
const cellStyleFormat = useCellFormatByFieldType(this._stylesheet, this._stylesheetFormats, columnDef, this._grid);
const autoDetectCellFormat = columnDef.excelExportOptions?.autoDetectCellFormat ?? this._excelExportOptions?.autoDetectCellFormat;
const cellStyleFormat = useCellFormatByFieldType(this._stylesheet, this._stylesheetFormats, columnDef, this._grid, autoDetectCellFormat);
// user could also override style and/or valueParserCallback
if (columnDef.excelExportOptions?.style) {
cellStyleFormat.stylesheetFormatterId = this._stylesheet.createFormat(columnDef.excelExportOptions.style).id;
Expand Down Expand Up @@ -638,7 +639,8 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ

// auto-detect best possible Excel format for Group Totals, unless the user provide his own formatting,
// we only do this check once per column (everything after that will be pull from temp ref)
if (fieldType === FieldType.number) {
const autoDetectCellFormat = columnDef.excelExportOptions?.autoDetectCellFormat ?? this._excelExportOptions?.autoDetectCellFormat;
if (fieldType === FieldType.number && autoDetectCellFormat !== false) {
let groupCellFormat = this._groupTotalExcelFormats[columnDef.id];
if (!groupCellFormat?.groupType) {
groupCellFormat = getExcelFormatFromGridFormatter(this._stylesheet, this._stylesheetFormats, columnDef, this._grid, 'group');
Expand Down
4 changes: 2 additions & 2 deletions packages/excel-export/src/excelUtils.ts
Expand Up @@ -41,12 +41,12 @@ export function parseNumberWithFormatterOptions(value: any, column: Column, grid
}

/** use different Excel Stylesheet Format as per the Field Type */
export function useCellFormatByFieldType(stylesheet: ExcelStylesheet, stylesheetFormatters: any, columnDef: Column, grid: SlickGrid) {
export function useCellFormatByFieldType(stylesheet: ExcelStylesheet, stylesheetFormatters: any, columnDef: Column, grid: SlickGrid, autoDetect = true) {
const fieldType = getColumnFieldType(columnDef);
let stylesheetFormatterId: number | undefined;
let callback: GetDataValueCallback = getExcelSameInputDataCallback;

if (fieldType === FieldType.number) {
if (fieldType === FieldType.number && autoDetect) {
stylesheetFormatterId = getExcelFormatFromGridFormatter(stylesheet, stylesheetFormatters, columnDef, grid, 'cell').stylesheetFormatter.id;
callback = getExcelNumberCallback;
}
Expand Down
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 839b09a

Please sign in to comment.