Skip to content

Commit

Permalink
fix(exports): Date should always export w/Formatter unless false (#856)
Browse files Browse the repository at this point in the history
* fix(exports): Date should always export w/Formatter unless false
- it should always execute Date formatter even when undefined, unless `exportWithFormatter` is specifically set to False in that case it will return original Date input
  • Loading branch information
ghiscoding authored Dec 21, 2022
1 parent 9d29e59 commit 1b249e8
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 334 deletions.
161 changes: 158 additions & 3 deletions packages/common/src/services/__tests__/utilities.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { EventSubscription } from '@slickgrid-universal/event-pub-sub';
import { of } from 'rxjs';

import { FieldType, OperatorType } from '../../enums/index';
import { GridOption } from '../../interfaces/index';
import { Column, GridOption } from '../../interfaces/index';
import { RxJsResourceStub } from '../../../../../test/rxjsResourceStub';
import {
addTreeLevelByMutation,
Expand All @@ -16,8 +16,10 @@ import {
findItemInTreeStructure,
findOrDefault,
formatNumber,
getColumnFieldType,
getDescendantProperty,
getTranslationPrefix,
isColumnDateType,
mapMomentDateFormatWithFieldType,
mapFlatpickrDateFormatWithFieldType,
mapOperatorByFieldType,
Expand Down Expand Up @@ -100,7 +102,7 @@ describe('Service/Utilies', () => {
});

it('should throw an error when argument provided is not a Promise neither an Observable', async () => {
expect(() => castObservableToPromise(rxjs, null)).toThrowError('Something went wrong,');
expect(() => castObservableToPromise(rxjs, null as any)).toThrowError('Something went wrong,');
});

it('should return original Promise when argument is already a Promise', async () => {
Expand Down Expand Up @@ -445,7 +447,24 @@ describe('Service/Utilies', () => {
});
});

describe('getDescendantProperty method', () => {
describe('getColumnFieldType() method', () => {
it('should return field type when type is defined', () => {
const result = getColumnFieldType({ type: FieldType.dateIso } as Column);
expect(result).toEqual(FieldType.dateIso);
});

it('should return outputType when both field type and outputType are defined', () => {
const result = getColumnFieldType({ outputType: FieldType.number, type: FieldType.dateIso } as Column);
expect(result).toEqual(FieldType.number);
});

it('should return string field type when neither type nor outputType are defined', () => {
const result = getColumnFieldType({ field: 'startDate' } as Column);
expect(result).toEqual(FieldType.string);
});
});

describe('getDescendantProperty() method', () => {
let obj = {};
beforeEach(() => {
obj = { id: 1, user: { firstName: 'John', lastName: 'Doe', address: { number: 123, street: 'Broadway' } } };
Expand Down Expand Up @@ -496,6 +515,142 @@ describe('Service/Utilies', () => {
});
});

describe('isColumnDateType() method', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('should return True when FieldType.date is provided', () => {
const result = isColumnDateType(FieldType.date);
expect(result).toBe(true);
});

it('should return True when FieldType.dateTime is provided', () => {
const result = isColumnDateType(FieldType.dateTime);
expect(result).toBe(true);
});

it('should return True when FieldType.dateTimeIso is provided', () => {
const result = isColumnDateType(FieldType.dateTimeIso);
expect(result).toBe(true);
});

it('should return True when FieldType.dateTimeShortIso is provided', () => {
const result = isColumnDateType(FieldType.dateTimeShortIso);
expect(result).toBe(true);
});

it('should return True when FieldType.dateTimeIsoAmPm is provided', () => {
const result = isColumnDateType(FieldType.dateTimeIsoAmPm);
expect(result).toBe(true);
});

it('should return True when FieldType.dateTimeIsoAM_PM is provided', () => {
const result = isColumnDateType(FieldType.dateTimeIsoAM_PM);
expect(result).toBe(true);
});

it('should return True when FieldType.dateEuro is provided', () => {
const result = isColumnDateType(FieldType.dateEuro);
expect(result).toBe(true);
});

it('should return True when FieldType.dateEuroShort is provided', () => {
const result = isColumnDateType(FieldType.dateEuroShort);
expect(result).toBe(true);
});

it('should return True when FieldType.dateTimeEuro is provided', () => {
const result = isColumnDateType(FieldType.dateTimeEuro);
expect(result).toBe(true);
});

it('should return True when FieldType.dateTimeShortEuro is provided', () => {
const result = isColumnDateType(FieldType.dateTimeShortEuro);
expect(result).toBe(true);
});

it('should return True when FieldType.dateTimeEuroAmPm is provided', () => {
const result = isColumnDateType(FieldType.dateTimeEuroAmPm);
expect(result).toBe(true);
});

it('should return True when FieldType.dateTimeEuroAM_PM is provided', () => {
const result = isColumnDateType(FieldType.dateTimeEuroAM_PM);
expect(result).toBe(true);
});

it('should return True when FieldType.dateTimeEuroShort is provided', () => {
const result = isColumnDateType(FieldType.dateTimeEuroShort);
expect(result).toBe(true);
});

it('should return True when FieldType.dateTimeEuroShortAM_PM is provided', () => {
const result = isColumnDateType(FieldType.dateTimeEuroShortAM_PM);
expect(result).toBe(true);
});

it('should return True when FieldType.dateTimeEuroShortAmPm is provided', () => {
const result = isColumnDateType(FieldType.dateTimeEuroShortAmPm);
expect(result).toBe(true);
});

it('should return True when FieldType.dateUs is provided', () => {
const result = isColumnDateType(FieldType.dateUs);
expect(result).toBe(true);
});

it('should return True when FieldType.dateUsShort is provided', () => {
const result = isColumnDateType(FieldType.dateUsShort);
expect(result).toBe(true);
});

it('should return True when FieldType.dateTimeUs is provided', () => {
const result = isColumnDateType(FieldType.dateTimeUs);
expect(result).toBe(true);
});

it('should return True when FieldType.dateTimeShortUs is provided', () => {
const result = isColumnDateType(FieldType.dateTimeShortUs);
expect(result).toBe(true);
});

it('should return True when FieldType.dateTimeUsAmPm is provided', () => {
const result = isColumnDateType(FieldType.dateTimeUsAmPm);
expect(result).toBe(true);
});

it('should return True when FieldType.dateTimeUsShortAM_PM is provided', () => {
const result = isColumnDateType(FieldType.dateTimeUsShortAM_PM);
expect(result).toBe(true);
});

it('should return True when FieldType.dateTimeUsAM_PM is provided', () => {
const result = isColumnDateType(FieldType.dateTimeUsAM_PM);
expect(result).toBe(true);
});

it('should return True when FieldType.dateTimeUsShort is provided', () => {
const result = isColumnDateType(FieldType.dateTimeUsShort);
expect(result).toBe(true);
});

it('should return True when FieldType.dateTimeUsShortAmPm is provided', () => {
const result = isColumnDateType(FieldType.dateTimeUsShortAmPm);
expect(result).toBe(true);
});

it('should return True when FieldType.dateUtc is provided', () => {
const result = isColumnDateType(FieldType.dateUtc);
expect(result).toBe(true);
});

it('should return True when FieldType.dateIso is provided', () => {
const result = isColumnDateType(FieldType.dateIso);
expect(result).toBe(true);
});
});

describe('mapMomentDateFormatWithFieldType method', () => {
it('should return a moment.js dateTime/dateTimeIso format', () => {
const output1 = mapMomentDateFormatWithFieldType(FieldType.dateTime);
Expand Down
93 changes: 57 additions & 36 deletions packages/common/src/services/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,46 @@ export function getTranslationPrefix(gridOptions?: GridOption): string {
return '';
}

/** From a column definition, find column type */
export function getColumnFieldType(columnDef: Column): typeof FieldType[keyof typeof FieldType] {
return columnDef.outputType || columnDef.type || FieldType.string;
}

/** Verify if the identified column is of type Date */
export function isColumnDateType(fieldType: typeof FieldType[keyof typeof FieldType]) {
switch (fieldType) {
case FieldType.date:
case FieldType.dateTime:
case FieldType.dateIso:
case FieldType.dateTimeIso:
case FieldType.dateTimeShortIso:
case FieldType.dateTimeIsoAmPm:
case FieldType.dateTimeIsoAM_PM:
case FieldType.dateEuro:
case FieldType.dateEuroShort:
case FieldType.dateTimeEuro:
case FieldType.dateTimeShortEuro:
case FieldType.dateTimeEuroAmPm:
case FieldType.dateTimeEuroAM_PM:
case FieldType.dateTimeEuroShort:
case FieldType.dateTimeEuroShortAmPm:
case FieldType.dateTimeEuroShortAM_PM:
case FieldType.dateUs:
case FieldType.dateUsShort:
case FieldType.dateTimeUs:
case FieldType.dateTimeShortUs:
case FieldType.dateTimeUsAmPm:
case FieldType.dateTimeUsAM_PM:
case FieldType.dateTimeUsShort:
case FieldType.dateTimeUsShortAmPm:
case FieldType.dateTimeUsShortAM_PM:
case FieldType.dateUtc:
return true;
default:
return false;
}
}

/**
* From a Date FieldType, return it's equivalent moment.js format
* refer to moment.js for the format standard used: https://momentjs.com/docs/#/parsing/string-format/
Expand Down Expand Up @@ -641,42 +681,23 @@ export function mapOperatorToShorthandDesignation(operator: OperatorType | Opera
export function mapOperatorByFieldType(fieldType: typeof FieldType[keyof typeof FieldType]): OperatorType {
let map: OperatorType;

switch (fieldType) {
case FieldType.unknown:
case FieldType.string:
case FieldType.text:
case FieldType.password:
case FieldType.readonly:
map = OperatorType.contains;
break;
case FieldType.float:
case FieldType.number:
case FieldType.date:
case FieldType.dateIso:
case FieldType.dateUtc:
case FieldType.dateTime:
case FieldType.dateTimeIso:
case FieldType.dateTimeIsoAmPm:
case FieldType.dateTimeIsoAM_PM:
case FieldType.dateEuro:
case FieldType.dateEuroShort:
case FieldType.dateTimeEuro:
case FieldType.dateTimeEuroAmPm:
case FieldType.dateTimeEuroAM_PM:
case FieldType.dateTimeEuroShort:
case FieldType.dateTimeEuroShortAmPm:
case FieldType.dateTimeEuroShortAM_PM:
case FieldType.dateUs:
case FieldType.dateUsShort:
case FieldType.dateTimeUs:
case FieldType.dateTimeUsAmPm:
case FieldType.dateTimeUsAM_PM:
case FieldType.dateTimeUsShort:
case FieldType.dateTimeUsShortAmPm:
case FieldType.dateTimeUsShortAM_PM:
default:
map = OperatorType.equal;
break;
if (isColumnDateType(fieldType)) {
map = OperatorType.equal;
} else {
switch (fieldType) {
case FieldType.unknown:
case FieldType.string:
case FieldType.text:
case FieldType.password:
case FieldType.readonly:
map = OperatorType.contains;
break;
case FieldType.float:
case FieldType.number:
default:
map = OperatorType.equal;
break;
}
}

return map;
Expand Down
12 changes: 6 additions & 6 deletions packages/excel-export/src/excelExport.service.spec.ts
Original file line number Diff line number Diff line change
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 { getExcelInputDataCallback, useCellFormatByFieldType } from './excelUtils';
import { getExcelSameInputDataCallback, useCellFormatByFieldType } from './excelUtils';

const pubSubServiceStub = {
publish: jest.fn(),
Expand Down Expand Up @@ -547,7 +547,7 @@ describe('ExcelExportService', () => {
excelExportOptions: { style: { font: { outline: true, italic: true }, format: '€0.00##;[Red](€0.00##)' }, width: 18 }
},
{ id: 'startDate', field: 'startDate', type: FieldType.dateIso, width: 100, exportWithFormatter: false, },
{ id: 'endDate', field: 'endDate', width: 100, formatter: Formatters.dateIso, type: FieldType.dateUtc, exportWithFormatter: true, outputType: FieldType.dateIso },
{ id: 'endDate', field: 'endDate', width: 100, formatter: Formatters.dateIso, type: FieldType.dateUtc, outputType: FieldType.dateIso },
] as Column[];

jest.spyOn(gridStub, 'getColumns').mockReturnValue(mockColumns);
Expand All @@ -557,7 +557,7 @@ describe('ExcelExportService', () => {
jest.clearAllMocks();
});

it(`should expect Date exported correctly when Field Type is provided and we use "exportWithFormatter" set to True & False`, async () => {
it(`should expect Date to be formatted as ISO Date only "exportWithFormatter" is undefined or set to True but remains untouched when "exportWithFormatter" is explicitely set to False`, async () => {
mockCollection = [
{ id: 0, userId: '1E06', firstName: 'John', lastName: 'X', position: 'SALES_REP', startDate: '2005-12-20T18:19:19.992Z', endDate: null },
{ id: 1, userId: '1E09', firstName: 'Jane', lastName: 'Doe', position: 'HUMAN_RESOURCES', startDate: '2010-10-09T18:19:19.992Z', endDate: '2024-01-02T16:02:02.000Z' },
Expand Down Expand Up @@ -585,12 +585,12 @@ describe('ExcelExportService', () => {
{ metadata: { style: 1, }, value: 'StartDate', },
{ metadata: { style: 1, }, value: 'EndDate', },
],
['1E06', 'John', 'X', 'SALES_REP', '2005-12-20', ''],
['1E09', 'Jane', 'Doe', 'HUMAN_RESOURCES', '2010-10-09', '2024-01-02'],
['1E06', 'John', 'X', 'SALES_REP', '2005-12-20T18:19:19.992Z', ''],
['1E09', 'Jane', 'Doe', 'HUMAN_RESOURCES', '2010-10-09T18:19:19.992Z', '2024-01-02'],
]
});
expect(service.regularCellExcelFormats.position).toEqual({
getDataValueParser: getExcelInputDataCallback,
getDataValueParser: getExcelSameInputDataCallback,
stylesheetFormatterId: 4,
});
});
Expand Down
15 changes: 12 additions & 3 deletions packages/excel-export/src/excelExport.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ import {
ExcelWorksheet,
FieldType,
FileType,
getColumnFieldType,
GetDataValueCallback,
GetGroupTotalValueCallback,
GridOption,
isColumnDateType,
KeyTitlePair,
Locale,
PubSubService,
Expand Down Expand Up @@ -554,10 +556,17 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ
}
} else {
let itemData: Date | number | string | ExcelCellFormat = '';
const fieldType = getColumnFieldType(columnDef);

// -- Read Data & Push to Data Array
// user might want to export with Formatter, and/or auto-detect Excel format, and/or export as regular cell data
itemData = exportWithFormatterWhenDefined(row, col, columnDef, itemObj, this._grid, this._excelExportOptions);

// 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)) {
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)
Expand All @@ -572,7 +581,7 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ
}
this._regularCellExcelFormats[columnDef.id] = cellStyleFormat;
}
const { stylesheetFormatterId, getDataValueParser: getDataValueParser } = this._regularCellExcelFormats[columnDef.id];
const { stylesheetFormatterId, getDataValueParser } = this._regularCellExcelFormats[columnDef.id];
itemData = getDataValueParser(itemData, columnDef, stylesheetFormatterId, this._stylesheet);

// does the user want to sanitize the output data (remove HTML tags)?
Expand Down Expand Up @@ -615,7 +624,7 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ

columns.forEach((columnDef) => {
let itemData: number | string | ExcelCellFormat = '';
const fieldType = columnDef.outputType || columnDef.type || FieldType.string;
const fieldType = getColumnFieldType(columnDef);
const skippedField = columnDef.excludeFromExport || false;

// if there's a exportCustomGroupTotalsFormatter or groupTotalsFormatter, we will re-run it to get the exact same output as what is shown in UI
Expand Down
Loading

0 comments on commit 1b249e8

Please sign in to comment.