Skip to content

Commit e77fdd2

Browse files
[WC-3334] Fix DG2 export type bugs (#2182)
2 parents b0836bb + b7255d4 commit e77fdd2

9 files changed

Lines changed: 693 additions & 173 deletions

File tree

packages/pluggableWidgets/datagrid-web/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1212

1313
### Fixed
1414

15+
- We fixed an issue where custom content columns ignored the export type setting, causing numbers and dates to always export as text in Excel.
16+
17+
- We fixed an issue where exported date values included a hidden time component even when the format specified date-only parts.
18+
19+
- We fixed an issue where boolean values were not exported as proper Excel boolean cells. Both attribute and custom content columns now export as native booleans (TRUE/FALSE) recognized by Excel.
20+
21+
- We fixed an issue where numbers with more than 15 significant digits lost precision during Excel export. Such values are now exported as text to preserve all digits.
22+
1523
- We fixed an issue where the vertical scrollbar disappeared after hiding a wide column while virtual scrolling was enabled.
1624
- We fixed an issue where only the first page loaded when the grid had enough columns to require horizontal scrolling.
1725

packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,28 @@ test.describe("datagrid-web export to Excel", () => {
2020
// Read file and convert to JSON.
2121
const workbook = XLSX.readFile("./e2e/downloads/testFilename.xlsx");
2222
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
23-
const jsonData = XLSX.utils.sheet_to_json(worksheet);
23+
const rawData = XLSX.utils.sheet_to_json(worksheet, { raw: true });
24+
const formattedData = XLSX.utils.sheet_to_json(worksheet, { raw: false });
2425

25-
expect(jsonData).toHaveLength(50);
26+
expect(rawData).toHaveLength(50);
2627

27-
expect(jsonData[0]).toEqual({
28+
// Verify raw cell types — numbers must be t:"n", not t:"s"
29+
expect(rawData[0]["Birth year"]).toBe(1983);
30+
expect(typeof rawData[0]["Birth year"]).toBe("number");
31+
expect(rawData[1]["Birth year"]).toBe(1970);
32+
expect(typeof rawData[1]["Birth year"]).toBe("number");
33+
34+
// Verify formatted display values
35+
expect(formattedData[0]).toEqual({
2836
"Birth date": "2/15/1983",
29-
"Birth year": 1983,
37+
"Birth year": "1983",
3038
"Color (enum)": "Black",
3139
"First name": "Loretta"
3240
});
3341

34-
expect(jsonData[1]).toEqual({
42+
expect(formattedData[1]).toEqual({
3543
"Birth date": "9/30/1970",
36-
"Birth year": 1970,
44+
"Birth year": "1970",
3745
"Color (enum)": "Red",
3846
"First name": "Chad"
3947
});

packages/pluggableWidgets/datagrid-web/src/features/data-export/DSExportRequest.ts

Lines changed: 3 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,8 @@
11
import { isAvailable } from "@mendix/widget-plugin-platform/framework/is-available";
2-
import Big from "big.js";
3-
import { DynamicValue, ListValue, ObjectItem, ValueStatus } from "mendix";
2+
import { ListValue, ObjectItem, ValueStatus } from "mendix";
43
import { createNanoEvents, Emitter, Unsubscribe } from "nanoevents";
5-
import { ColumnsType, ShowContentAsEnum } from "../../../typings/DatagridProps";
6-
7-
/** Represents a single Excel cell (SheetJS compatible) */
8-
interface ExcelCell {
9-
/** Cell type: 's' = string, 'n' = number, 'b' = boolean, 'd' = date */
10-
t: "s" | "n" | "b" | "d";
11-
/** Underlying value */
12-
v: string | number | boolean | Date;
13-
/** Optional Excel number/date format, e.g. "yyyy-mm-dd" or "$0.00" */
14-
z?: string;
15-
/** Optional pre-formatted display text */
16-
w?: string;
17-
}
18-
19-
type RowData = ExcelCell[];
20-
21-
type HeaderDefinition = {
22-
name: string;
23-
type: string;
24-
};
25-
26-
type ValueReader = (item: ObjectItem, props: ColumnsType) => ExcelCell;
27-
28-
type ReadersByType = Record<ShowContentAsEnum, ValueReader>;
29-
30-
type RowReader = (item: ObjectItem) => RowData;
4+
import { ColumnsType } from "../../../typings/DatagridProps";
5+
import { HeaderDefinition, RowData, readChunk } from "./cell-readers";
316

327
type ColumnReader = (props: ColumnsType) => HeaderDefinition;
338

@@ -262,132 +237,6 @@ export class DSExportRequest {
262237
}
263238
}
264239

265-
const readers: ReadersByType = {
266-
attribute(item, props) {
267-
const data = props.attribute?.get(item);
268-
269-
if (data?.status !== "available") {
270-
return makeEmptyCell();
271-
}
272-
273-
const value = data.value;
274-
const format = getCellFormat({
275-
exportType: props.exportType,
276-
exportDateFormat: props.exportDateFormat,
277-
exportNumberFormat: props.exportNumberFormat
278-
});
279-
280-
if (value instanceof Date) {
281-
return excelDate(format === undefined ? data.displayValue : value, format);
282-
}
283-
284-
if (typeof value === "boolean") {
285-
return excelBoolean(value);
286-
}
287-
288-
if (value instanceof Big || typeof value === "number") {
289-
const num = value instanceof Big ? value.toNumber() : value;
290-
return excelNumber(num, format);
291-
}
292-
293-
return excelString(data.displayValue ?? "");
294-
},
295-
296-
dynamicText(item, props) {
297-
const data = props.dynamicText?.get(item);
298-
299-
switch (data?.status) {
300-
case "available":
301-
const format = getCellFormat({
302-
exportType: props.exportType,
303-
exportDateFormat: props.exportDateFormat,
304-
exportNumberFormat: props.exportNumberFormat
305-
});
306-
307-
return excelString(data.value ?? "", format);
308-
case "unavailable":
309-
return excelString("n/a");
310-
default:
311-
return makeEmptyCell();
312-
}
313-
},
314-
315-
customContent(item, props) {
316-
const value = props.exportValue?.get(item).value ?? "";
317-
const format = getCellFormat({
318-
exportType: props.exportType,
319-
exportDateFormat: props.exportDateFormat,
320-
exportNumberFormat: props.exportNumberFormat
321-
});
322-
323-
return excelString(value, format);
324-
}
325-
};
326-
327-
function makeEmptyCell(): ExcelCell {
328-
return { t: "s", v: "" };
329-
}
330-
331-
function excelNumber(value: number, format?: string): ExcelCell {
332-
return {
333-
t: "n",
334-
v: value,
335-
z: format
336-
};
337-
}
338-
339-
function excelString(value: string, format?: string): ExcelCell {
340-
return {
341-
t: "s",
342-
v: value,
343-
z: format ?? undefined
344-
};
345-
}
346-
347-
function excelDate(value: string | Date, format?: string): ExcelCell {
348-
return {
349-
t: format === undefined ? "s" : "d",
350-
v: value,
351-
z: format
352-
};
353-
}
354-
355-
function excelBoolean(value: boolean): ExcelCell {
356-
return {
357-
t: "b",
358-
v: value,
359-
w: value ? "TRUE" : "FALSE"
360-
};
361-
}
362-
363-
interface DataExportProps {
364-
exportType: "default" | "number" | "date" | "boolean";
365-
exportDateFormat?: DynamicValue<string>;
366-
exportNumberFormat?: DynamicValue<string>;
367-
}
368-
369-
function getCellFormat({ exportType, exportDateFormat, exportNumberFormat }: DataExportProps): string | undefined {
370-
switch (exportType) {
371-
case "date":
372-
return exportDateFormat?.status === "available" ? exportDateFormat.value : undefined;
373-
case "number":
374-
return exportNumberFormat?.status === "available" ? exportNumberFormat.value : undefined;
375-
default:
376-
return undefined;
377-
}
378-
}
379-
380-
function createRowReader(columns: ColumnsType[]): RowReader {
381-
return item =>
382-
columns.map(col => {
383-
return readers[col.showContentAs](item, col);
384-
});
385-
}
386-
387-
function readChunk(data: ObjectItem[], columns: ColumnsType[]): RowData[] {
388-
return data.map(createRowReader(columns));
389-
}
390-
391240
declare global {
392241
interface Window {
393242
scheduler: {

0 commit comments

Comments
 (0)