Skip to content

[WC-3334] Fix DG2 export type bugs#2182

Open
samuelreichert wants to merge 20 commits into
mainfrom
fix/datagrid-export-type-bugs
Open

[WC-3334] Fix DG2 export type bugs#2182
samuelreichert wants to merge 20 commits into
mainfrom
fix/datagrid-export-type-bugs

Conversation

@samuelreichert
Copy link
Copy Markdown
Contributor

@samuelreichert samuelreichert commented Apr 20, 2026

Pull request type

Refactoring (e.g. file rename, variable rename, etc.)
Bug fix (non-breaking change which fixes an issue)

Description

Fix multiple data export bugs in DataGrid2's Excel export feature (WC-3334).

Problems

  1. customContent columns ignore exportType — exportValue always exports as text regardless of exportType setting, making numeric values unusable for calculations in Excel
  2. Date exports leak time components — Date-only formats (e.g., dd-MMM-yyyy) carry a hidden time component in the cell value, affecting sorting and calculations
  3. Boolean exports use string representation — Boolean values exported as text strings instead of native Excel boolean cells (t: "b")
  4. Long numbers lose precision — Big.toNumber() loses precision for values >15 significant digits, causing scientific notation in Excel

Changes

  • Extracted cell-creation logic from DSExportRequest.ts into standalone cell-readers.ts module (pure functions, zero behavior change)
  • customContent reader now branches on exportType: parses to Number() for "number", new Date() for "date", string boolean matching for "boolean", with graceful string fallback on parse failure
  • Added hasTimeComponent(format) / stripTime(date) helpers — when format has no h/s tokens, date values are normalized to midnight UTC
  • excelDate now always produces t: "d" cells with a format code; when no explicit format is configured, reads the app's locale date pattern from window.mx.session.getConfig().locale.patterns.date and converts it to an Excel-compatible format
  • excelBoolean now returns { t: "b", v: boolean } — native Excel boolean cells (TRUE/FALSE)
  • customContent boolean parsing accepts case-insensitive: true/false, yes/no, 1/0
  • Big values with >15 significant digits export as strings via toFixed() to preserve all digits
  • Added typings/global.d.ts for window.mx type declarations
  • 35 unit tests covering all reader paths, edge cases, and fallback behavior

@samuelreichert samuelreichert marked this pull request as ready for review April 20, 2026 14:06
@samuelreichert samuelreichert requested a review from a team as a code owner April 20, 2026 14:06
@samuelreichert samuelreichert force-pushed the fix/datagrid-export-type-bugs branch 2 times, most recently from b804adb to 05d8784 Compare April 24, 2026 13:57
@samuelreichert samuelreichert force-pushed the fix/datagrid-export-type-bugs branch from 05d8784 to bc6c7a0 Compare April 30, 2026 07:52
Comment thread packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts Outdated
@samuelreichert samuelreichert requested a review from iobuhov May 1, 2026 12:36
@samuelreichert samuelreichert force-pushed the fix/datagrid-export-type-bugs branch 3 times, most recently from 08b3fd2 to 66ebfc8 Compare May 7, 2026 14:02
iobuhov
iobuhov previously approved these changes May 7, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

AI Code Review

🔶 Changes requested — one or more medium-severity items must be addressed


What was reviewed

File Change
packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts New module: extracted cell-reader logic from DSExportRequest, added customContent type-branching, boolean→Yes/No, date time-stripping, Big precision guard
packages/pluggableWidgets/datagrid-web/src/features/data-export/DSExportRequest.ts Removed inline reader logic, imports readChunk from new module
packages/pluggableWidgets/datagrid-web/src/features/data-export/__tests__/cell-readers.spec.ts New test file: 28 tests covering reader paths, fallbacks, precision, and time-stripping
packages/pluggableWidgets/datagrid-web/CHANGELOG.md Added four ### Fixed entries under [Unreleased]

Skipped (out of scope): dist/, pnpm-lock.yaml


Findings

🔶 Medium — excelDate type signature allows {t:"d", v: string} — invalid SheetJS cell

File: packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts line 71
Problem: excelDate accepts value: string | Date and format?: string. When a caller passes a string value with a non-undefined format, the function returns { t: "d", v: "<string>" } — an invalid SheetJS cell (SheetJS requires t:"d" cells to have a Date as v). The current callers happen not to hit this combination, but the type signature doesn't prevent it. A future caller could easily produce a corrupt export.
Fix: Overload or split the function so t:"d" is only produced when v is a Date:

export function excelDate(value: string): ExcelCell;
export function excelDate(value: Date, format: string): ExcelCell;
export function excelDate(value: string | Date, format?: string): ExcelCell {
    return {
        t: format === undefined || typeof value === "string" ? "s" : "d",
        v: value,
        z: format
    };
}

🔶 Medium — dynamicText reader computes format but never uses it for numeric/date coercion

File: packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts line 142
Problem: The dynamicText reader calls getCellFormat(...) and passes format to excelString. However, excelString only uses format to set the z field — there is no coercion to t:"n" or t:"d" regardless of exportType. This means a dynamicText column with exportType: "number" still exports as a string cell, which is the bug described in problem #1 of the PR for customContent. The same silent no-op exists here but is untested and undocumented.
Fix: Either apply the same number/date branching as customContent, or explicitly document and test that dynamicText always exports as string (and why). If the intent is string-only, remove the getCellFormat call to avoid confusion.


⚠️ Low — hasTimeComponent regex misses minute-only formats like "mm:ss"

File: packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts line 86
Note: /[hs]/i detects h (hours) and s (seconds). A format like "mm:ss" (minutes and seconds, no hours) would match s and correctly preserve time — that path is fine. However, a format of "HH:mm" (hours and minutes, locale variants) would also match H via the case-insensitive flag — also correct. The one edge case not covered by a test is "ss" alone (elapsed seconds format) — worth adding a test case for documentation. No code change required if the current behavior is intentional.


⚠️ Low — excelString has redundant ?? undefined

File: packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts line 67
Note: z: format ?? undefined — since format is already string | undefined, this is a no-op. Simplify to z: format.


⚠️ Low — Plain number type path in attribute reader is untested

File: packages/pluggableWidgets/datagrid-web/src/features/data-export/__tests__/cell-readers.spec.ts
Note: The attribute reader at line 131 has a branch for typeof value === "number" (native JS number, not Big). All tests use new Big(...). Since Mendix's ListAttributeValue for numeric types always returns Big, this branch is likely dead code — if so, remove it. If it can be reached, add a test with a plain number builder or a comment explaining when it applies.


Positives

  • Clean extraction into a pure-function module (cell-readers.ts) with zero side effects — easy to test and reason about.
  • The countSignificantDigits guard for Big precision handles leading zeros, decimals, and negative values correctly.
  • stripTime uses Date.UTC consistently, avoiding local-timezone contamination in exported date values.
  • Boolean fix (Yes/No string cell instead of {t:"b"}) matches grid display and avoids the Excel TRUE/FALSE rendering issue cleanly.
  • Test structure uses the column() factory helper from test-utils correctly, keeping tests concise and avoiding repetition.
  • CHANGELOG entries are clear, user-facing, and cover all four fixed bugs.

@samuelreichert samuelreichert force-pushed the fix/datagrid-export-type-bugs branch 2 times, most recently from 4e8b769 to f494395 Compare May 12, 2026 14:10
@samuelreichert samuelreichert requested a review from iobuhov May 12, 2026 14:11
@github-actions
Copy link
Copy Markdown

AI Code Review

⚠️ Approved with suggestions — low-severity items only, safe to merge


What was reviewed

File Change
packages/pluggableWidgets/datagrid-web/CHANGELOG.md Four new Fixed entries under Unreleased
packages/pluggableWidgets/datagrid-web/src/features/data-export/DSExportRequest.ts Removed all inline cell-creation helpers; imports readChunk, HeaderDefinition, RowData from new module
packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts New module with all cell-reader logic, exported helpers, and readChunk
packages/pluggableWidgets/datagrid-web/src/features/data-export/__tests__/cell-readers.spec.ts 28 new unit tests covering all reader paths

Skipped (out of scope): dist/, pnpm-lock.yaml


Findings

⚠️ Low — hasTimeComponent regex misses minutes token m

File: packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts line 89
Note: The regex /[hs]/i matches h (hours) and s (seconds) but not m (minutes). A format like "hh:mm" would correctly match on h, but a pure "mm:ss" (minutes + seconds without an hour component) would not match h — it would still match s, so that edge case is covered. However, a format like "HH:MM" (uppercase) in a custom SheetJS format where only M = minutes is used would be missed entirely. In practice, any real time format includes h/H, so this is unlikely to cause a real regression, but the regex is under-documented.
Fix: Add a comment explaining which tokens are checked, or broaden to /[hms]/i to be safe:

// h = hours, m = minutes, s = seconds
function hasTimeComponent(format: string): boolean {
    return /[hms]/i.test(format);
}

⚠️ Low — exportValue read without checking .status

File: packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts line 157
Note: props.exportValue?.get(item).value ?? "" accesses .value without first checking .status === "available". If the value is in "loading" state, .value may be undefined (hence the ?? ""), but this silently emits an empty cell rather than signalling the loading state. The attribute and dynamicText readers both gate on .status; customContent does not. This was the same in the original code, so it's not a regression introduced here, but since the module is being rewritten it's a good opportunity to align.
Fix:

customContent(item, props) {
    const raw = props.exportValue?.get(item);
    if (!raw || raw.status !== "available") {
        return makeEmptyCell();
    }
    const value = raw.value ?? "";
    // ... rest of reader
}

⚠️ Low — excelDate overloads allow Date without format to slip through at runtime

File: packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts lines 71–79
Note: The two overload signatures enforce that Date requires a format argument at compile time, but the runtime implementation t: value instanceof Date && format !== undefined ? "d" : "s" silently falls back to "s" if a Date is passed without a format (which TypeScript should prevent, but any-typed call sites bypass this). The original code had the same shape; no regression introduced here, but since this is a clean extraction it's worth tightening.
Fix: Add a runtime guard or a non-null assertion with a comment, to make the intent explicit and catch unexpected call sites:

export function excelDate(value: string | Date, format?: string): ExcelCell {
    if (value instanceof Date && format === undefined) {
        // Should not happen — TypeScript overloads require format for Date
        return makeEmptyCell();
    }
    return {
        t: value instanceof Date ? "d" : "s",
        v: value,
        z: format
    };
}

Positives

  • Clean separation of concerns: extracting all cell-creation logic into a pure-function module (cell-readers.ts) with zero side effects makes the code straightforward to test and reason about in isolation.
  • Test coverage is thorough — all four reader types, edge cases (NaN, empty strings, parse failures), and the new time-stripping logic are all explicitly exercised using @mendix/widget-plugin-test-utils builders rather than manual mock objects.
  • The countSignificantDigits approach using Big.toFixed() + string manipulation is correct and avoids floating-point representation issues that value.toNumber() would introduce for big integers.
  • CHANGELOG entries are clear, user-facing, and follow the Keep a Changelog format already established in the file.

@samuelreichert samuelreichert force-pushed the fix/datagrid-export-type-bugs branch from f494395 to 5aef9f2 Compare May 13, 2026 13:49
@github-actions
Copy link
Copy Markdown

AI Code Review

🔶 Changes requested — one or more medium-severity items must be addressed


What was reviewed

File Change
packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts New module — extracted cell-creation logic with bug fixes for customContent exportType, date time-stripping, boolean display, and Big precision
packages/pluggableWidgets/datagrid-web/src/features/data-export/DSExportRequest.ts Deleted old in-file readers; now imports readChunk + types from cell-readers.ts
packages/pluggableWidgets/datagrid-web/src/features/data-export/__tests__/cell-readers.spec.ts 28 new unit tests covering all reader paths
packages/pluggableWidgets/datagrid-web/CHANGELOG.md Four entries added under ### Fixed in [Unreleased]

Skipped (out of scope): dist/, pnpm-lock.yaml


Findings

🔶 Medium — Whitespace-only strings silently export as the number 0

File: packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts line 164–167
Problem: Number(" ") evaluates to 0, not NaN. So when a user's exportValue expression returns a whitespace-only string and exportType is "number", the guard value !== "" passes, !Number.isNaN(0) is true, and the cell is exported as the number 0 — silently discarding the actual content. This differs from the intent of the fallback documented in the PR description.
Fix:

if (props.exportType === "number" && value.trim() !== "") {
    const parsed = Number(value);
    if (!Number.isNaN(parsed)) {
        return excelNumber(parsed, format);
    }
}

A companion test:

it("falls back to string for whitespace-only value with number exportType", () => {
    const col = column("Ws", c => {
        c.showContentAs = "customContent";
        c.exportValue = listExpression(() => "   ");
        c.exportType = "number";
    });
    const cell = readSingleCell(col);
    expect(cell.t).toBe("s");
    expect(cell.v).toBe("   ");
});

⚠️ Low — hasTimeComponent regex matches the S in locale specifiers

File: packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts line 89
Note: /[hs]/i (case-insensitive) matches any h, H, s, or S in the format string. A locale-tagged format like "[$-en-US]mmmm d, yyyy" contains a capital S in US, so hasTimeComponent incorrectly returns true and the time is not stripped — even though the format is date-only. Locale-tagged formats are uncommon in this context, but worth hardening. One approach: strip bracket-delimited locale tokens before testing, or use a stricter token regex:

function hasTimeComponent(format: string): boolean {
    // Strip locale tags like [$-en-US] before checking for time tokens
    const stripped = format.replace(/\[.*?\]/g, "");
    return /[hs]/i.test(stripped);
}

⚠️ Low — typeof value === "number" branch was removed without a comment

File: packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts line 133
Note: The old attribute reader handled both value instanceof Big and typeof value === "number". The new code only handles Big. If a Mendix attribute ever surfaces a plain JS number (e.g., an integer constant from a calculated attribute), it falls through to excelString(data.displayValue ?? "") — exporting a number as text without any warning. If this branch was intentionally dropped because Mendix guarantees numeric attributes are always Big, add a brief comment or an exhaustive check to make that contract explicit.


⚠️ Low — Missing test for dynamicText reader unavailable path

File: packages/pluggableWidgets/datagrid-web/src/features/data-export/__tests__/cell-readers.spec.ts
Note: The dynamicText reader returns excelString("n/a") when data.status === "unavailable" (line 150 of cell-readers.ts), but there is no test covering this branch. Since "n/a" is a meaningful user-visible fallback, add a test:

it("returns n/a cell when dynamicText is unavailable", () => {
    const col = column("Label", c => {
        c.showContentAs = "dynamicText";
        c.dynamicText = listExpression(() => "text", "unavailable");  // or however the builder sets unavailable
    });
    const cell = readSingleCell(col);
    expect(cell.t).toBe("s");
    expect(cell.v).toBe("n/a");
});

⚠️ Low — ### Fixed placed before ### Added in CHANGELOG

File: packages/pluggableWidgets/datagrid-web/CHANGELOG.md
Note: Keep a Changelog specifies section order as Added → Changed → Deprecated → Removed → Fixed → Security. The new entries put ### Fixed above the existing ### Added block. Not blocking, but worth reordering for consistency.


Positives

  • Clean extraction of readers into a dedicated cell-readers.ts module — pure functions, no side-effects, easy to unit-test in isolation.
  • Overloaded excelDate signatures enforce at the type level that a Date value requires a format argument, preventing accidental date-as-string cells.
  • countSignificantDigits correctly handles negatives, decimals, and leading zeros via progressive .replace chaining rather than fragile regex.
  • The value !== "" guard before Number(value) in customContent prevents the known Number("") === 0 false positive — the whitespace edge case is the only remaining gap.
  • 28 tests cover all three reader paths plus edge cases (loading, unavailable, precision overflow, time-stripping, fallback on parse failure) — thorough coverage for a bug-fix PR.

@github-actions
Copy link
Copy Markdown

AI Code Review

🔶 Changes requested — one or more medium-severity items must be addressed


What was reviewed

File Change
packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts New module — all cell-reader logic extracted here; includes new boolean/number/date type coercion for customContent
packages/pluggableWidgets/datagrid-web/src/features/data-export/DSExportRequest.ts Removed old readers/helpers; now imports from cell-readers
packages/pluggableWidgets/datagrid-web/src/features/data-export/__tests__/cell-readers.spec.ts New unit test file — 35 tests covering all reader paths
packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js Added { raw: false } to sheet_to_json; updated "Birth year" assertions from number to string
packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts Import order change only
packages/pluggableWidgets/datagrid-web/typings/global.d.ts New — declares optional window.mx with session/locale types
packages/pluggableWidgets/datagrid-web/CHANGELOG.md Four fixed-bug entries added to unreleased section

Skipped (out of scope): dist/, pnpm-lock.yaml

CI checks could not be fetched (requires approval) — verify green before merging.


Findings

🔶 Medium — E2E assertion weakened by raw: false; no longer verifies cell type

File: packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js line 27–46
Problem: Passing { raw: false } to XLSX.utils.sheet_to_json forces every cell value to be returned as a formatted string, regardless of the underlying SheetJS cell type (t: "n", t: "d", t: "b"). As a result the "Birth year" assertion changed from 1983 (number) to "1983" (string). The test can no longer distinguish a proper number cell from a string cell containing "1983" — which is exactly the kind of type-correctness bug this PR fixes. Use raw: true (the default) for type-checking assertions, and add a second assertion that reads { raw: false } only if you need to verify display formatting.
Fix:

// Verify raw cell types (the core bug fix — numbers are t:"n", not t:"s")
const rawData = XLSX.utils.sheet_to_json(worksheet, { raw: true });
expect(rawData[0]["Birth year"]).toBe(1983);           // number cell
expect(typeof rawData[0]["Birth year"]).toBe("number");

// Verify formatted output separately if needed
const formattedData = XLSX.utils.sheet_to_json(worksheet, { raw: false });
expect(formattedData[0]["Birth date"]).toBe("2/15/1983");

⚠️ Low — customContent reader reads .value without checking .status

File: packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts line 165
Note: props.exportValue?.get(item).value ?? "" accesses .value without first checking that .status === "available". For a loading or unavailable exportValue, value is undefined and the ?? "" silently converts it to an empty string — indistinguishable from a column that genuinely has an empty string value. The Mendix pattern is to gate on .status before reading .value. This is consistent with how the attribute and dynamicText readers already handle it.

customContent(item, props) {
    const raw = props.exportValue?.get(item);
    if (!raw || raw.status !== "available") {
        return makeEmptyCell();
    }
    const value = raw.value ?? "";
    // ... rest of reader
}

⚠️ Low — String fallback cells carry a z format code that has no effect

File: packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts line 198
Note: excelString(value, format) is called in the fallback path of customContent (when number/date parse fails). SheetJS ignores z on t: "s" string cells — the format code is silently dropped by Excel. This is harmless but misleading: a reader returning { t: "s", v: "not-a-number", z: "#,##0.00" } implies a numeric format was applied when it was not. Call excelString(value) (no format) for all fallback cases.


Positives

  • countSignificantDigits correctly uses toFixed() (not toString() or toPrecision()) to get the canonical digit string, then strips sign, decimal point, and leading zeros — a subtle but correct approach.
  • hasTimeComponent strips [...] locale tags before regex-testing for h/s tokens, preventing false positives from locale codes like [$-en-US].
  • stripTime uses Date.UTC + getUTC* throughout — timezone-safe and correct for all locales.
  • global.d.ts declares window.mx as optional (mx?:), matching the defensive ?. usage in getDefaultDateFormat.
  • All four CHANGELOG entries are clear and user-facing, following Keep a Changelog format exactly.
  • Unit tests use @mendix/widget-plugin-test-utils builders throughout — no manual mock objects.
  • The describe nesting groups by reader type then by behaviour (precision, time-stripping) — easy to scan.

@samuelreichert samuelreichert force-pushed the fix/datagrid-export-type-bugs branch from 46321d8 to bf67c2c Compare May 22, 2026 12:18
rahmanunver
rahmanunver previously approved these changes May 22, 2026
iobuhov
iobuhov previously approved these changes May 22, 2026
Copy link
Copy Markdown
Collaborator

@iobuhov iobuhov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

samuelreichert and others added 16 commits May 22, 2026 16:03
…number

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…digits

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove "b" from ExcelCell.t union and boolean from ExcelCell.v
since excelBoolean now returns string cells. Fix misleading test
name for undefined dynamicText case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add overloads to excelDate so t:"d" is only produced when v is a Date,
preventing invalid SheetJS cells. Remove dead plain-number branch in
attribute reader (Mendix always returns Big for numeric types). Drop
no-op getCellFormat call in dynamicText reader (pre-rendered strings
have no raw typed value to coerce). Remove redundant `?? undefined`
in excelString.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Number("   ") === 0, so the old value !== "" check silently exported
whitespace strings as 0. Use value.trim() !== "" instead.

hasTimeComponent matched "S" in locale tags like [$-en-US], causing
date-only formats to incorrectly retain a time component. Strip
bracket-delimited tokens before testing.

Also documents that Mendix numeric attributes are always Big — the
plain-number branch was not accidentally omitted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dynamicText

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ngelog

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…om string fallback

Check exportValue status before reading .value to avoid silent empty-string
on loading/unavailable. Drop format arg from fallback excelString — SheetJS
ignores z on t:"s" cells so passing it was misleading.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split sheet_to_json into raw:true and raw:false passes. The raw pass
verifies t:"n" cell type (the core bug fix); the formatted pass
verifies display values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@samuelreichert samuelreichert force-pushed the fix/datagrid-export-type-bugs branch from bf67c2c to 73d5735 Compare May 22, 2026 14:06
leonardomendix
leonardomendix previously approved these changes May 22, 2026
rahmanunver
rahmanunver previously approved these changes May 22, 2026
@github-actions
Copy link
Copy Markdown

AI Code Review

🔶 Changes requested — one or more medium-severity items must be addressed


What was reviewed

File Change
packages/pluggableWidgets/datagrid-web/CHANGELOG.md 4 new Fixed entries added
packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js Export test updated to verify raw cell types vs formatted display values
packages/pluggableWidgets/datagrid-web/src/features/data-export/DSExportRequest.ts Reader logic extracted to cell-readers.ts; only imports and readChunk call remain
packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts New module — all cell reader functions and ExcelCell types
packages/pluggableWidgets/datagrid-web/src/features/data-export/__tests__/cell-readers.spec.ts New unit tests (35 cases) covering all reader paths
packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts Import reorder only
packages/pluggableWidgets/datagrid-web/typings/global.d.ts New file — window.mx type declaration

Skipped (out of scope): dist/, pnpm-lock.yaml

CI checks could not be fetched due to permission constraints — author should confirm all checks are green before merge.


Findings

🔶 Medium — E2E export test describe block is missing afterEach session logout

File: packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js lines 7–49
Problem: The "datagrid-web export to Excel" describe block has no test.afterEach to log out the Mendix session. Per the E2E guidelines this is mandatory — each test leaves an open session, and the Mendix license caps concurrent sessions at 5, causing CI failures when tests run in parallel or are re-run.
Fix:

test.describe("datagrid-web export to Excel", () => {
    test.afterEach("Cleanup session", async ({ page }) => {
        await page.evaluate(() => window.mx.session.logout());
    });

    test("check if export to Excel generates correct output", async ({ page }) => {
        // ...existing test body
    });
});

⚠️ Low — dynamicText reader silently drops the format string — contradicts "zero behavior change" claim

File: packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts line 156
Note: The old dynamicText reader passed format (from getCellFormat) to excelString, setting the z field on the cell. The new reader omits it entirely (return excelString(data.value ?? "")). In practice z on a t:"s" cell has no effect in Excel, so the visible output is identical — but the PR description promises "zero behavior change" and this is a code-level difference. If any downstream consumer reads the raw SheetJS cell object it will see a missing z. Consider either explicitly commenting that the z field is intentionally omitted for string cells, or restoring it for consistency.


⚠️ Low — Manual mock object instead of builder for dynamicText unavailable test

File: packages/pluggableWidgets/datagrid-web/src/features/data-export/__tests__/cell-readers.spec.ts line 368
Note: c.dynamicText = { get: () => ({ status: "unavailable", value: undefined }) } as any creates a raw object instead of using the test-utils builders. This is brittle — it doesn't cover status: "loading" and will diverge if the Mendix API shape changes.
Fix:

import { listExpression } from "@mendix/widget-plugin-test-utils";
// unavailable state via builder — check the test-utils API for the correct method,
// e.g. listExpression(...).withStatus("unavailable") or the equivalent.

Positives

  • countSignificantDigits correctly handles Big's internal toFixed() representation, including negative values and decimals — catches the >15 significant-digit precision loss cleanly.
  • hasTimeComponent strips [$-locale] tags before the regex so locale strings with S characters (e.g. [$-en-US]) don't false-positive as time formats.
  • customContent reader now properly guards raw.status !== "available" before reading the value — consistent with how the attribute reader checks data status.
  • global.d.ts types window.mx as optional (mx?), matching the optional-chaining call site in getDefaultDateFormat().
  • CHANGELOG entries are specific and user-facing — good format for release notes.
  • All four original export bugs have unit test coverage, including edge cases (whitespace, empty strings, case-insensitive boolean values, Big precision boundary at exactly 15 digits).

@github-actions
Copy link
Copy Markdown

AI Code Review

⚠️ Approved with suggestions — low-severity items only, safe to merge


What was reviewed

File Change
packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts New module extracting cell-creation logic; adds number precision guard, date-time stripping, boolean cell support, locale-aware date format
packages/pluggableWidgets/datagrid-web/src/features/data-export/DSExportRequest.ts Removed inlined reader code; now delegates to cell-readers.ts
packages/pluggableWidgets/datagrid-web/src/features/data-export/__tests__/cell-readers.spec.ts 35 new unit tests covering all reader paths
packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts Import order reformat only
packages/pluggableWidgets/datagrid-web/typings/global.d.ts New window.mx type declaration
packages/pluggableWidgets/datagrid-web/CHANGELOG.md Four new Fixed entries for the export bugs
packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js Updated export E2E assertions to use raw/formatted dual-pass
packages/pluggableWidgets/line-chart-web/e2e/LineChart.spec.js Removed waitForMendixApp, added scroll+visible guard for dimension test
packages/pluggableWidgets/video-player-web/e2e/VideoPlayer.spec.js Replaced image-load polling with waitForDataReady; added scroll before screenshot

Skipped (out of scope): dist/, pnpm-lock.yaml

CI checks: gh pr checks required approval in this environment — CI status could not be confirmed automatically.


Findings

⚠️ Low — hasTimeComponent regex also matches S (seconds alias) in some locale format strings

File: packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts line 99
Note: The regex /[hs]/i strips locale tags before testing, which is correct. However SheetJS/Excel format tokens for seconds are ss (lowercase s), and the regex already catches s. The concern is the i flag making H and S match too — S is not a standard Excel time token, so this is harmless in practice, but a reader might be surprised. A comment documenting which tokens are matched (h = hours, s = seconds in SheetJS format strings) would prevent future accidental widening of the regex.


⚠️ Low — countSignificantDigits counts trailing zeros after decimal as significant

File: packages/pluggableWidgets/datagrid-web/src/features/data-export/cell-readers.ts line 108–113
Note: new Big("1.000000000000000").toFixed() produces "1.000000000000000" (15 zeros after decimal). The current implementation strips leading zeros before the decimal but not trailing zeros after it, so this value would be counted as 16 significant digits and exported as a string, even though it's representable as 1 with full precision. This is a conservative edge-case over-escape (string export is safe, just unexpectedly stringified), not data loss. Consider value.toFixed().replace(/\.?0+$/, "") before stripping or using value.toPrecision() to count.


⚠️ Low — DataGrid.spec.js export test missing afterEach session logout

File: packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js line 7
Note: The datagrid-web export to Excel describe block has no test.afterEach session logout. All other describe blocks in the same file also lack it, so this is pre-existing, but the PR touched this section. Mendix's license allows 5 concurrent sessions; in long CI runs this can cause flaky failures. Adding:

test.afterEach("Cleanup session", async ({ page }) => {
    await page.evaluate(() => window.mx.session.logout());
});

to the export describe block would prevent the leak.


⚠️ Low — LineChart.spec.js removed waitForMendixApp without a replacement ready-signal

File: packages/pluggableWidgets/line-chart-web/e2e/LineChart.spec.js line 4–6
Note: The beforeEach now only does page.goto("/") with no wait for the Mendix app to be ready. The individual tests compensate by waiting for toBeVisible() on each element, but the app may not have bootstrapped yet when the first test in each describe block runs. The removed waitForMendixApp was providing a reliable app-ready signal. If these tests are currently flaky in CI, consider adding waitForLoadState("networkidle") in beforeEach as a lightweight substitute.


Positives

  • countSignificantDigits correctly handles negative numbers and integers with no decimal point — the replace("-", "") and replace(".", "") steps are in the right order.
  • The locale-tag stripping in hasTimeComponent (/\[.*?\]/g) is a non-obvious correctness detail done right — without it, [$-en-US] locale codes containing S would incorrectly suppress time stripping.
  • customContent reader falls back gracefully to string on every parse failure path (NaN number, invalid date, unrecognized boolean), preserving the original value for the user rather than silently emitting an empty cell.
  • The Big precision threshold is conservative: exporting as string is safe, so erring on the side of caution is the right call.
  • Unit tests use listAttribute, listExpression, and dynamic builders from @mendix/widget-plugin-test-utils — no hand-rolled mock objects.
  • E2E assertion split into raw (type-checking) + formatted (display-value) passes is a meaningful improvement over the previous single-pass assertion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants