Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Note
Copilot was unable to run its full agentic suite in this review.
Pull request overview
This PR migrates Excel read/write and file parsing away from SheetJS (xlsx) to @jetstreamapp/simple-excel, switching to streaming workbook generation and passing around raw bytes for Google Drive downloads, while improving user-facing error handling (e.g., password-protected workbooks).
Changes:
- Replace
xlsxusage with@jetstreamapp/simple-excelacross UI utilities, exports, and loaders (streaming writer + workbook reader). - Update Google Drive selectors and relevant types to pass file bytes instead of parsed workbooks.
- Add tested, centralized file-parse error messaging and expand/adjust unit tests for new behavior.
Reviewed changes
Copilot reviewed 29 out of 31 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/replace-package-deps.mjs | Stops preserving xlsx dependency when swapping dependencies. |
| scripts/build-electron.mjs | Removes xlsx from dependency-removal list during Electron build. |
| package.json | Adds @jetstreamapp/simple-excel and removes xlsx tarball dependency. |
| libs/ui/src/lib/form/file-selector/GoogleFileSelectorExternalButton.tsx | Stops parsing XLSX client-side; forwards downloaded bytes to consumers. |
| libs/ui/src/lib/form/file-selector/GoogleFileSelector.tsx | Converts gapi binary strings to bytes; forwards unparsed bytes downstream. |
| libs/ui/src/lib/file-download-modal/tests/RecordDownloadModal.spec.tsx | Awaits async download path; adds Blob assertion for Excel writer output. |
| libs/ui/src/lib/file-download-modal/RecordDownloadModal.tsx | Awaits async Excel generation; updates Excel error handling behavior. |
| libs/ui/src/lib/file-download-modal/FileDownloadModal.tsx | Makes Excel generation async; expands supported data types to include Blob. |
| libs/types/src/lib/ui/types.ts | Changes InputReadGoogleSheet contract from workbook to { name, bytes }. |
| libs/shared/ui-utils/src/lib/shared-ui-utils.ts | Replaces XLSX utilities with simple-excel writer/reader + sniffing-based parse pipeline. |
| libs/shared/ui-utils/src/lib/load-multi-object-template.utils.ts | Adds streaming reader for multi-object template sheets via simple-excel. |
| libs/shared/ui-utils/src/lib/file-parse-error.utils.ts | Introduces centralized user-facing parse error messaging. |
| libs/shared/ui-utils/src/lib/tests/shared-ui-utils.spec.ts | Updates/adds tests for streaming Excel writer and workbook reader behavior. |
| libs/shared/ui-utils/src/lib/tests/prepare-load-multi-object-template.spec.ts | Updates round-trip test to use simple-excel reader. |
| libs/shared/ui-utils/src/lib/tests/file-parse-error.utils.spec.ts | Adds fixture-based tests for encrypted workbook and error-messaging rules. |
| libs/shared/ui-utils/src/index.ts | Exports the new file-parse error utility. |
| libs/shared/ui-core/src/jobs/Jobs.tsx | Surfaces truncated-cell warnings in job success messages/status. |
| libs/shared/ui-core/src/jobs/JobWorker.ts | Streams Excel generation with progress + cancellation; reports truncation counts. |
| libs/features/sobject-export/src/sobject-export-utils.ts | Uses sanitizeSheetName to prevent worksheet name collisions after truncation. |
| libs/features/sobject-export/src/tests/sobject-export-utils.spec.ts | Adds tests for worksheet name sanitization/de-duplication behavior. |
| libs/features/manage-permissions/src/utils/permission-manager-export-utils.ts | Rewrites permission export XLSX generation to streaming writer with merges/styles. |
| libs/features/manage-permissions/src/utils/tests/permission-manager-field-export.spec.ts | Reworks tests to validate generated XLSX (rows, styles, merges, truncation). |
| libs/features/manage-permissions/src/ManagePermissionsEditor.tsx | Awaits async workbook generation and wires truncation notifications. |
| libs/features/load-records/src/steps/SelectObjectAndFile.tsx | Uses parseFile and centralized parse error messaging for local/Drive files. |
| libs/features/load-records-multi-object/src/useProcessLoadFile.ts | Accepts workbook bytes, uses centralized parse errors + toast on failure. |
| libs/features/load-records-multi-object/src/load-records-multi-object-utils.ts | Parses template via streaming reader; improves dataset construction robustness. |
| libs/features/load-records-multi-object/src/tests/load-records-multi-object-utils.spec.ts | Writes real XLSX fixtures via writer; parses real template file; adds new edge-case tests. |
| libs/features/load-records-multi-object/src/LoadRecordsMultiObject.tsx | Removes XLSX parsing; feeds bytes into the new workbook parser. |
| apps/jetstream-desktop-client/src/app/components/core/useElectronActionLoader.tsx | Uses centralized file-parse error messaging for Electron open-with flows. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🔵 Needs a closer look
Unresolved issues remain in permission-export memory use, cancellation, warning propagation, and CSV byte detection.
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (5)
Previously missed (2) — in code that hasn't changed since the last review.
libs/shared/ui-core/src/jobs/JobWorker.ts:384
- Cancellation is only checked from
onProgress. If a cancel message arrives after the last progress callback—or if this writer emits no progress for a small workbook—prepareExcelFilecan resolve and this success response is posted, so the UI reports/downloads a job the user canceled. Re-checkcanceledJobIdsafter the awaited write and before constructing the success response.
libs/shared/ui-core/src/jobs/Jobs.tsx:438 - This warning is lost on the Google Drive fallback path:
handleGoogleUploadFailureoverwritesnewJob.statusMessagewith its generic message (and persists that object when there is no token or the upload rejects). Thus agdriveexport containing truncated cells no longer tells the user that values were shortened, despite settingfinished-warninghere. Preserve/appendtruncationWarningwhen threading this job through the Google-upload failure handler.
libs/features/manage-permissions/src/utils/permission-manager-export-utils.ts:282
- This path still builds the full
dataRowsarray and thengetColumnWidths([...headerRows, ...dataRows], ...)creates a second converted copy of every cell before the writer starts. Large Permission Manager exports therefore retain roughly two worksheet-sized representations, undermining the memory-reduction goal and still risking browser exhaustion. Compute widths from a bounded sample (or while generating rows) and stream the full rows without retaining the complete arrays.
return { headerRows, dataRows, merges, columns: getColumnWidths([...headerRows, ...dataRows], new Set([0])) };
libs/shared/ui-utils/src/lib/shared-ui-utils.ts:1379
- The byte path only sends data to PapaParse when
sniff(head)returnstext. Valid CSV/TSV inputs that are UTF-16 (an Excel/Drive export with anFF FEBOM) returnunknown, and an empty CSV returnsempty; both are then sent toparseWorkbookand reported as an unreadable XLSX. Use the filename/extension as a fallback for CSV/TSV and decode the BOM-selected encoding before parsing.
const head = source instanceof Blob ? new Uint8Array(await source.slice(0, SNIFF_BYTES).arrayBuffer()) : source.subarray(0, SNIFF_BYTES);
if (sniff(head) === 'text') {
return parseCsvContent(await bytesToText(source), options);
}
return parseWorkbook(source, options);
scripts/build-electron.mjs:59
@jetstreamapp/simple-excelis now imported by the renderer through@jetstream/shared/ui-utils, so Nx will include it in the desktop target dependencies. This cleanup list removes dependencies bundled into the renderer, but the replacement for the removedxlsxentry is missing; the package will remain installed and unnecessarily ship in the desktop artifact. Add@jetstreamapp/simple-excelto the removal list.
return ['react', 'tslib', 'stripe'].filter((dep) => {
- Files reviewed: 29/31 changed files
- Comments generated: 0 new
- Review effort level: Lite
ee06a9d to
1eef536
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved findings cover OAuth redirects, development-port selection, email persistence, Google Drive export, cancellation cleanup, truncation messaging, and duplicate-download guards.
Get a fresh assessment by requesting another Copilot review.
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (3)
apps/api/src/app/controllers/oauth.controller.ts:95
- This always sends the OAuth result back to
JETSTREAM_CLIENT_URL. If Vite selects a fallback development port because 4200 is occupied, the browser started OAuth on (for example) 4210 but the callback redirects to 4200, breaking the login flow for that worktree; preserve the originating client URL rather than hard-coding the configured origin.
const clientUrl = new URL(ENV.JETSTREAM_CLIENT_URL).origin;
const returnParams: OauthLinkParams = {
type: 'salesforce',
clientUrl,
};
libs/api-config/src/lib/email.config.ts:71
- This fire-and-forget insert lets
sendEmailresolve before theemail_activityrow is persisted. Cron callers can finish or callprocess.exit()before the row is written, so the Cloudflare cooldown query can miss a sent alert and send duplicates; await the insert while retaining the existing error handling.
.catch((err) => logger.error({ message: err?.message }, '[EMAIL][ERROR] Error logging email activity'));
libs/shared/ui-core/src/jobs/JobWorker.ts:399
- A canceled workbook build is reported here, but its id is never removed from
canceledJobIds. Every canceled background download therefore remains in the worker's Set for its lifetime, causing unbounded growth and stale cancellation state; delete the id after computing/reporting the cancellation error (or in afinallyblock).
logger.error('Error in BulkDownload job:', ex);
- Files reviewed: 41/43 changed files
- Comments generated: 7
- Review effort level: Lite
1eef536 to
3b9d7fb
Compare
|
Went through Copilot's suppressed (low-confidence) comments across both reviews — several were valid:
|
There was a problem hiding this comment.
🟡 Changes recommended
Four unresolved moderate findings remain around Drive parse warnings and async export cancellation and concurrency.
Get a fresh assessment by requesting another Copilot review.
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (2)
libs/features/load-records/src/steps/SelectObjectAndFile.tsx:164
- This Drive path drops the
errorsreturned byparseFile, unlike the local path immediately above. A malformed CSV downloaded from Drive can therefore be accepted without the warning that the same file gets locally; preserve these errors and apply the same warning/suppression logic here (or share that helper).
const { data: rawData, headers } = await parseFile(bytes, { onParsedMultipleWorkbooks, extension: getExtensionFromName(name) });
const { data, removedCount } = removeEmptyRows(rawData);
libs/ui/src/lib/file-download-modal/RecordDownloadModal.tsx:599
- The async workbook build can now outlive this modal: Cancel/onClose remains available while
handleDownloadis awaitingprepareExcelFile, but no cancellation or closed guard is set. A user can cancel and still get a latersaveFile/Google upload (and the handler can continue after unmount); coordinate this button and modal close path with an abort or completion guard.
<button
className="slds-button slds-button_brand"
onClick={handleDownload}
disabled={invalidConfig || isLoadingChildRelationships || isPreparingFile}
- Files reviewed: 29/31 changed files
- Comments generated: 2
- Review effort level: Lite
3b9d7fb to
52b5305
Compare
|
Went through Copilot's suppressed (low-confidence) comments from the latest review — both were valid:
|
There was a problem hiding this comment.
🟡 Changes recommended
Eight moderate review findings remain unresolved across parsing, async state handling, progress reporting, and export memory usage.
Get a fresh assessment by requesting another Copilot review.
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (7)
libs/features/load-records-multi-object/src/load-records-multi-object-utils.ts:370
- This fallback only adds a
dataerror and leavessobjectundefined, butvalidateObjectDatauses the absence of ansobjecterror to calldescribeSObject(org, dataset.sobject). For an unreadable sheet that reaches validation,describeSObjectreceivesundefined(and itsstartsWithaccess throws), producing an extra misleadingThe object "undefined" could not be loaded...error instead of preserving the single worksheet-read failure. Make validation skip metadata lookup when the fallback has no object value, or mark this dataset as unreadable before validation.
/** Stand-in for a worksheet that could not be read at all, so the rest of the workbook is still usable */
function buildUnreadableDataset(sheetName: string): LoadMultiObjectData {
const dataset: Partial<LoadMultiObjectData> = {
worksheet: sheetName,
data: [],
dataById: {},
headers: [],
referenceHeaders: new Set(),
errors: [
{
property: 'data',
worksheet: sheetName,
location: WORKSHEET_LOCATIONS.dataStartCell,
locationType: 'CELL',
message: `Jetstream could not read the data on this worksheet. Check that headers are on row 5, data starts on row 6, and the sheet matches the template layout.`,
},
],
libs/shared/ui-core/src/jobs/JobWorker.ts:309
progress.currentis the number of rows emitted by the writer, including header rows and (for subqueries) rows from additional sheets, butJob.tsxrenderscurrent of totalwhenevertotal > 0. UsingdownloadedRecords.lengththerefore shows impossible values such as1001 of 1000and is misleading for multi-sheet exports; keep the total indeterminate until the writer reports a matching workbook-wide total.
total: downloadedRecords.length,
libs/shared/ui-utils/src/lib/shared-ui-utils.ts:1375
- The
.jsonextension check happens before the byte sniffer, so any byte-backed file named.jsonis forced throughparseJsoneven when its bytes are an XLSX, encrypted workbook, or legacy format. That bypasses the new format detection and produces a generic JSON error instead of the intended workbook-specific guidance (the code already handles a CSV/XLSX mismatch by sniffing). Keep the string-JSON fast path, but sniff byte inputs before deciding whether to callparseJson.
options = options || {};
if (options.extension === INPUT_ACCEPT_FILETYPES.JSON) {
// FileSelector reads .json as text, but the signature permits bytes, so decode rather than assume
return parseJson(isString(content) ? content : await bytesToText(content));
}
libs/shared/ui-utils/src/lib/shared-ui-utils.ts:1395
- This branch now throws
XlsxErrorfor encrypted/legacy/non-XLSX byte inputs, butCreateFieldsImportExport.handleImportstill invokesparseFilewithout atry/catch(and passes it as an asynconReadFilecallback with noonError). Selecting one of those files in the Create Fields import therefore produces an unhandled rejection instead of the promised actionable toast; update that caller (and any similar parse entrypoint) to usegetFileParseErrorMessage.
return parseWorkbook(source, options);
libs/ui/src/lib/file-download-modal/FileFauxDownloadModal.tsx:174
- This new catch silently discards any rejection from the async
onDownloadcallback. For example,handleManifestDownloadawaitsgetPackageXml; if that request fails, the modal has already closed and this catch provides no toast or callback to report the failed download. Propagate the rejection or add an explicit error callback instead of leaving async failures invisible.
} catch {
// TODO: show error message somewhere
libs/ui/src/lib/form/file-selector/GoogleFileSelector.tsx:109
- Parsing is now delegated to the consumer, and
LoadRecordsSelectObjectAndFile.handleGoogleFileis async, but this callback is still invoked without awaiting its Promise. The selector therefore clearsloadingimmediately after the bytes are handed off, allowing a refresh or second picker selection while the previous workbook is still being parsed; the two async parses can race and overwrite the selected data. AllowonReadFileto return a promise and await it before clearing the loading state.
// The gapi client hands back the response body as a binary string (one byte per char code) rather than
// bytes, so it is converted once here and every consumer downstream works with the file's actual bytes.
onReadFile({
name: selectedItem.name || '',
bytes: binaryStringToBytes(resultBody).buffer as ArrayBuffer,
selectedFile: selectedItem,
});
libs/ui/src/lib/form/file-selector/GoogleFileSelectorExternalButton.tsx:132
- This path has the same race after parsing moved out of the selector:
onReadFilecan start an async consumer parse, but it is not awaited beforesetDownloading(false)infinally. That re-enables the external picker while the previous parse is still running, so selecting another file can leave results applied out of order. Make the callback promise-aware and await it before ending the download/loading state.
// The file is handed on unparsed - whoever receives it decides how to read it and reports its own errors
setSelectedFile(syntheticDoc);
setManagedFilename(fileName);
callbackRefs.current.onReadFile && callbackRefs.current.onReadFile({ name: fileName, bytes, selectedFile: syntheticDoc });
- Files reviewed: 30/32 changed files
- Comments generated: 1
- Review effort level: Lite
|
Went through Copilot's suppressed (low-confidence) comments — several were valid:
The rest were already implemented (post-write cancel re-check, |
387bd76 to
6f7178f
Compare
6f7178f to
c29a664
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved issues affect unreadable-sheet previews, failed Drive selection state, permission export memory usage, and truncation warning propagation.
Get a fresh assessment by requesting another Copilot review.
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (3)
libs/features/load-records-multi-object/src/load-records-multi-object-utils.ts:370
- This fallback is still rendered by the worksheet preview/config UI, which reads
dataset.sobjectanddataset.operationas populated values. Leaving both fields absent produces anundefined • undefinedsummary and an unusable operation selector for every unreadable sheet; either model the unreadable state explicitly and hide that editor, or provide a dedicated non-editable placeholder for it.
function buildUnreadableDataset(sheetName: string): LoadMultiObjectData {
const dataset: Partial<LoadMultiObjectData> = {
worksheet: sheetName,
data: [],
dataById: {},
headers: [],
referenceHeaders: new Set(),
errors: [
libs/features/manage-permissions/src/utils/permission-manager-export-utils.ts:44
- Although the workbook writer streams rows after this point, each worksheet generator still appends every exported row to
dataRowsbeforewriteWorksheetstarts. Large permission tables therefore retain the full cell matrix in memory and can still exhaust the browser heap; makedataRowsan iterable/generator and materialize only the row currently being written.
* A sheet assembled in memory before it is streamed into the workbook.
*
* The writer has no worksheet object to decorate after the fact - rows, merges and column widths all have to be
* known by the time the sheet is opened - so each `generate*Worksheet` returns this and `writeWorksheet` does the
* writing for all four sheets.
libs/shared/ui-core/src/jobs/Jobs.tsx:353
- This warning is preserved for the non-bulk Google path below, but the bulk-API Google path calls
uploadToGoogleDrivewithout passingwarning: truncationWarning. If that upload fails,handleGoogleUploadFailureoverwrites the status with the generic local-fallback message, so the user is not told that the fallback file contains truncated cells. Pass the warning through that bulk upload call as well.
const truncationWarning = truncatedCells
? ` ${formatNumber(truncatedCells)} ${pluralizeFromNumber('value', truncatedCells)} exceeded Excel's cell limit and ${
truncatedCells === 1 ? 'was' : 'were'
} truncated.`
: '';
- Files reviewed: 34/36 changed files
- Comments generated: 2
- Review effort level: Lite
|
Went through Copilot's suppressed (low-confidence) comments from every review on this PR. One was valid:
One is worth recording as wrong, because following it would break the desktop build:
The rest were already handled:
|
3548d33 to
079b258
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Three moderate issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (2)
libs/features/manage-permissions/src/utils/permission-manager-export-utils.ts:138
- The field worksheet contains
Datevalues, and the sharedprepareExcelFileexplicitly usesdates: 'local'for Excel serial conversion. This custom writer omits that option, so non-UTC clients can get audit timestamps shifted to the engine's default timezone even though the headers and CSV export promise local time. Passdates: 'local'here as well.
cellOverflow: 'truncate',
// Every permission cell is TRUE or FALSE and every object name repeats across sheets, so the bounded shared
// string table is worth its memory here - this is what `bookSST: true` used to give us
strings: 'auto',
});
libs/shared/ui-utils/src/lib/shared-ui-utils.ts:1447
openWorkbookForImportselectserrors: 'null', but these rows are returned without normalizing null/undefined cells. Unlike the previoussheet_to_json(..., defval: '')path, a formula-error or blank cell can therefore reach the standard load-records field mapping asnullinstead of a blank string. Map row values withvalue ?? ''before returning, as the multi-object reader already does.
const { rows, headers } = await workbook.sheet(selectedSheet).toObjects({ dropEmptyHeaders: true });
return { data: rows, headers, errors: [] };
- Files reviewed: 35/37 changed files
- Comments generated: 1
- Review effort level: Lite
|
Checked the two new suppressed comments from the latest review — both are wrong, and I verified each against the engine rather than the docs:
Everything else in the suppressed blocks was covered in my earlier comment. |
079b258 to
1338328
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Two unresolved moderate issues affect invalid Google workbook state handling and successful Google upload completion.
Get a fresh assessment by requesting another Copilot review.
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (1)
libs/ui/src/lib/file-download-modal/FileDownloadModal.tsx:307
- After the upload job is emitted,
handleUploadToGooglecloses the modal; the new unmount cleanup aborts this controller, so this check is true for every successful Google upload.handleDownloadtherefore returns beforesaveFileFormatToStorageandtrackEventrun. Move the close until after those completion steps, or otherwise distinguish an upload that has already been queued from a cancelled build.
if (excelOptions.signal.aborted) {
return;
- Files reviewed: 36/38 changed files
- Comments generated: 1
- Review effort level: Lite
|
Checked the new suppressed comment — invalid, and I added a test so it stays that way.
That abort check is still doing real work, just not for this case: a user who cancels mid-build makes |
1338328 to
95616a8
Compare
95616a8 to
81373dd
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
Unresolved issues remain with warning accessibility, workbook format fallback, and failed-export side effects.
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (3)
libs/shared/ui-core/src/jobs/Jobs.tsx:360
finished-warningis now used for exports with truncated cells, butJob.tsxrenders that status with the accessible descriptionjob success(lines 120–127). Screen-reader users therefore hear a lossy export announced as successful without any warning; add a warning-specific accessible description/icon treatment for this status.
status: truncationWarning ? 'finished-warning' : 'success',
statusMessage: `Records downloaded successfully.${truncationWarning}`,
libs/shared/ui-utils/src/lib/shared-ui-utils.ts:1378
- The extension fallback is applied to every non-zip classification. A renamed/encrypted or legacy CFB workbook sniffed as
cfb-encrypted/cfb-legacywith a.csvextension is therefore decoded as CSV and can return a misleading CSV result instead of the format-specificXlsxErrormessage promised by the workbook opener. Restrict this fallback toempty/unknownkinds so known binary formats take precedence.
kind === 'text' || encoding !== 'utf-8' || (kind !== 'zip' && TEXT_EXTENSIONS.has(normalizeExtension(options.extension)));
libs/ui/src/lib/file-download-modal/FileFauxDownloadModal.tsx:172
- Awaiting this callback treats a caught export failure as success.
ManagePermissionsEditor.handleExportcatches workbook-generation errors, shows a toast, and resolves normally, so this modal still tracksfile_downloadand persists the selected format even though no file was saved. Make the callback rethrow after reporting, or return an explicit success result and gate these side effects on it.
await onDownload({ fileName, fileFormat: _fileFormat, mimeType, uploadToGoogle, googleFolder });
- Files reviewed: 37/39 changed files
- Comments generated: 0 new
- Review effort level: Lite
81373dd to
880b3be
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
Two moderate findings remain unresolved, along with a Permission Manager warning-message mismatch.
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (3)
libs/features/manage-permissions/src/ManagePermissionsEditor.tsx:524
- Passing the generic truncation notifier here makes the warning tell Permission Manager users to download as CSV or JSON, but this modal only offers CSV, XLSX, and Google Drive (and its CSV export is a ZIP); JSON is not an available alternative. Use a Permission Manager-specific message or make the notifier accept the caller's available formats.
// Any value past Excel's per-cell character limit is truncated by the writer, which the user needs to know about
{ onCellsTruncated: notifyExcelCellsTruncated },
libs/shared/ui-utils/src/lib/load-multi-object-template.utils.ts:398
rows()omits completely blank rows unlessblankRows: true(as the writer test has to request explicitly), butbuildDatasetlater treats each compactdataRowsindex as the physical Excel row viagetExcelRow. If a user leaves an empty row between records, duplicate/reference/graph errors for every later record point to the wrong worksheet row. Preserve the source row number while streaming (or request blank rows and skip them without losing their index).
for await (const row of sheet.rows({ startRow: headerRow })) {
libs/ui/src/lib/file-download-modal/FileDownloadModal.tsx:216
handleUploadToGooglecallsonModalClose()after queuing the job, which unmounts this component and runs the abort cleanup; therefore this signal is already aborted when control returns here, so the function returns beforesaveFileFormatToStorageandtrackEvent. The new Google-upload test expects those completion side effects, so return an explicit “upload queued” result fromhandleUploadToGoogle(withfalsefor cancellation) instead of using the controller’s signal after the modal closes.
await handleUploadToGoogle(excelOptions);
if (abortController.signal.aborted) {
return;
}
- Files reviewed: 38/40 changed files
- Comments generated: 0 new
- Review effort level: Lite
|
Went through Copilot's suppressed (low-confidence) comments from the latest review — one was valid:
The rest didn't apply — notably |
… import and export Excel files are now written and read by @jetstreamapp/simple-excel, a streaming, dependency-free engine built and verified against Excel, Google Sheets, Numbers and LibreOffice. Exports stream rows into a Blob, so memory no longer grows with row count and the large downloads that failed with RangeError now complete. Columns are sized to their content (header as the floor, first 500 rows sampled, 8 to 60 character units), so ids, dates and labels are readable without resizing. Cells over 32,767 characters are truncated and reported on every path: interactive downloads keep their warning toast, background jobs finish in a warning state with the count (kept through the Google Drive upload and its failure fallback), and the Permission Manager export, which used to fail outright, truncates and warns too. Background Excel downloads show a live row count and can be cancelled while the file is being built, including a cancel that lands after the last progress tick. A job that finishes with warnings is now announced as such to a screen reader instead of being read out as a plain success. Imports sniff the file first: password-protected workbooks, legacy .xls, .xlsb and .ods files get a message that says what to do instead of a half-working load, and keep getting it when the file has been renamed to .csv to get around the refusal; CSV bytes from Drive or the desktop open-with path go to the CSV parser, UTF-16 text (Excel's Unicode Text save) included; error cells read as blank; escaped control characters and CRLF survive; a blank header cell in the middle of a multi-object template no longer shifts the following columns onto the wrong fields. Worksheet names are sanitized and de-duplicated the way Excel requires, so two objects whose names collide within 31 characters no longer overwrite each other in the SObject Export workbook. Permission Manager header rows are bold. The xlsx package, its codepage table and the build-script special cases for it are removed; the desktop build strips the renderer-bundled engine from the packaged app.
880b3be to
e2aa78c
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The template reader mishandles error cells, permission exports omit the large-file row-count hint, and one new test does not verify its format-specific cases.
Get a fresh assessment by requesting another Copilot review.
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
- Files reviewed: 38/40 changed files
- Comments generated: 3
- Review effort level: Lite
| ): Promise<void> { | ||
| // No `header` option - these sheets have two header rows, and it only writes one. Column widths come from the | ||
| // column-label header row (the merged group-header row above it would dwarf every column) and a sample of the data | ||
| const sheet = workbook.addSheet(sheetName, { columns: getExcelColumnWidths(headerRows[headerRows.length - 1], dataRows) }); |
| function getTemplateCellText(cells: Map<string, RawCell>, address: string): string | null { | ||
| const value = cells.get(address)?.value; | ||
| if (value == null) { | ||
| return null; | ||
| } | ||
| return String(value).trim() || null; | ||
| } |
| test.each([ | ||
| { uploadSource: 'csv', allowedTypes: ['csv', 'xlsx', 'gdrive'] }, | ||
| { uploadSource: 'xlsx', allowedTypes: ['xlsx', 'gdrive'] }, | ||
| ] as const)('records the format and analytics after a queued $uploadSource upload closes the modal', async ({ allowedTypes }) => { | ||
| const { trackEvent, emitUploadToGoogleEvent } = setup([...allowedTypes]); | ||
|
|
||
| await userEvent.click(screen.getByLabelText(/Google Drive/i)); | ||
| await userEvent.click(screen.getByRole('button', { name: /^Download$/i })); | ||
|
|
||
| await waitFor(() => expect(screen.getByText('modal closed')).toBeTruthy()); | ||
|
|
||
| expect(emitUploadToGoogleEvent).toHaveBeenCalledTimes(1); | ||
| expect(trackEvent).toHaveBeenCalledWith('file_download', { | ||
| source: 'test', | ||
| fileFormat: 'gdrive', | ||
| component: 'FileDownloadModal', | ||
| }); | ||
| expect(localStorage.getItem(LS_KEY)).toBe('gdrive'); | ||
| }); |
GOALS:
Excel files are now written and read by @jetstreamapp/simple-excel, a streaming, dependency-free engine built and verified against Excel, Google Sheets, Numbers and LibreOffice. Exports stream rows into a Blob, so memory no longer grows with row count and the large downloads that failed with RangeError now complete; cells over 32,767 characters are truncated and reported on every path, including background jobs and the Permission Manager export, which used to fail outright. Background Excel downloads show a live row count and can be cancelled while the file is being built.
Imports sniff the file first: password-protected workbooks, legacy .xls, .xlsb and .ods files get a message that says what to do instead of a half-working load, CSV bytes from Drive or the desktop open-with path go to the CSV parser, error cells read as blank, escaped control characters and CRLF survive, and a blank header cell in the middle of a multi-object template no longer shifts the following columns onto the wrong fields.
Worksheet names are sanitized and de-duplicated the way Excel requires, so two objects whose names collide within 31 characters no longer overwrite each other in the SObject Export workbook. Permission Manager header rows are bold. The xlsx package, its codepage table and the build-script special cases for it are removed.