feat(files): stream large CSV previews and add import-as-table#5125
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview A new GET workspace CSV preview route resolves the file by ID and reads from the authoritative storage key (not trusting the client key alone). Streaming is implemented in When preview is truncated, a warning toast offers Import as a table, which starts the existing async table import against the file already in workspace storage (no re-upload). Async import accepts optional Save and edit/split/preview controls are hidden for stream-only large CSVs in the Files page and mothership view via shared Reviewed by Cursor Bugbot for commit d9de35d. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0321342. Configure here.
Greptile SummaryThis PR fixes OOM crashes when opening large CSVs in the file viewer by introducing a server-side streaming preview route that reads only the first 1,000 rows from storage, adds a size-threshold guard (
Confidence Score: 5/5Safe to merge — the streaming logic, access controls, and import-flag semantics are all correct. The core OOM fix (server-side streaming, browser-side size gate), the loading-guard scoping to CSV-only, and the deleteSourceFile opt-out semantics are all implemented correctly and backed by tests. There are no broken code paths in the changed routes or components. mothership-view.tsx — the isActiveCsv check uses extension-only identification, which is inconsistent with isCsvStreamOnly's MIME-aware check; low-impact in practice but worth aligning. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Browser
participant FileViewer
participant CsvTablePreview
participant API as /api/workspaces/[id]/files/[fileId]/csv-preview
participant Storage
participant ImportAPI as /api/table/import-async
Browser->>FileViewer: Open CSV file
FileViewer->>FileViewer: isCsvStreamOnly(file) ?
alt "file.size > 5MB"
FileViewer->>CsvTablePreview: render read-only preview
CsvTablePreview->>API: GET csv-preview (key, fileId, workspaceId)
API->>API: checkSessionOrInternalAuth
API->>API: getUserEntityPermissions
API->>API: getWorkspaceFile(workspaceId, fileId) — verify key matches
API->>Storage: downloadFileStream (stream only)
Storage-->>API: Readable stream
API->>API: getCsvPreviewSlice (first 1000 rows)
API-->>CsvTablePreview: "{ headers, rows, truncated }"
CsvTablePreview->>Browser: Render DataTable
note over CsvTablePreview: truncated=true → fire Import as a table toast
Browser->>ImportAPI: POST import-async (fileKey, deleteSourceFile:false)
ImportAPI->>Storage: stream entire file
ImportAPI-->>Browser: "{ tableId, importId }"
else "file.size <= 5MB"
FileViewer->>Browser: TextEditor (editable)
note over Browser: parseCsv caps DataTable preview at 1000 rows, triggers same toast if truncated
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Browser
participant FileViewer
participant CsvTablePreview
participant API as /api/workspaces/[id]/files/[fileId]/csv-preview
participant Storage
participant ImportAPI as /api/table/import-async
Browser->>FileViewer: Open CSV file
FileViewer->>FileViewer: isCsvStreamOnly(file) ?
alt "file.size > 5MB"
FileViewer->>CsvTablePreview: render read-only preview
CsvTablePreview->>API: GET csv-preview (key, fileId, workspaceId)
API->>API: checkSessionOrInternalAuth
API->>API: getUserEntityPermissions
API->>API: getWorkspaceFile(workspaceId, fileId) — verify key matches
API->>Storage: downloadFileStream (stream only)
Storage-->>API: Readable stream
API->>API: getCsvPreviewSlice (first 1000 rows)
API-->>CsvTablePreview: "{ headers, rows, truncated }"
CsvTablePreview->>Browser: Render DataTable
note over CsvTablePreview: truncated=true → fire Import as a table toast
Browser->>ImportAPI: POST import-async (fileKey, deleteSourceFile:false)
ImportAPI->>Storage: stream entire file
ImportAPI-->>Browser: "{ tableId, importId }"
else "file.size <= 5MB"
FileViewer->>Browser: TextEditor (editable)
note over Browser: parseCsv caps DataTable preview at 1000 rows, triggers same toast if truncated
end
Reviews (3): Last reviewed commit: "fix(files): scope mothership preview-tog..." | Re-trigger Greptile |
…, fix sniff perf and toggle flash
…le-view # Conflicts: # apps/sim/lib/table/import-runner.ts
|
Addressed the review findings (commit 81ca970) + merged staging (56f941c):
Also: staging shipped this same source-preservation feature as @greptile review |
|
Scoped the mothership preview-toggle loading guard to CSV files only (d9de35d) — non-CSV rich types (markdown, html, svg, mermaid) no longer lose the toggle during cold loads. @greptile review |

Summary
response.text()OOM'd on multi-GB files)keepSourceflag so the source file isn't deleted; the success toast offers a "View tables" buttonisCsvStreamOnlypredicate)Type of Change
Testing
csv-preview-slice(slice, truncation boundary, delimiter detection, early stream-abort) — 9 passing; existing import suites green (40)type-check,biome lint,check:api-validation:strictall passChecklist