feat: Improve Parquet Viewer - #2557
Conversation
🎩 PreviewA preview build has been created at: |
This stack of pull requests is managed by Graphite. Learn more about stacking. |
There was a problem hiding this comment.
Pull request overview
This PR improves the Parquet artifact viewer by switching to range-request-backed Parquet reads (so previews aren’t bounded by full file size) and by enriching the table UI with Parquet metadata (row/column counts) plus a downloadable schema JSON.
Changes:
- Replace full-file Parquet fetching with
hyparquetasync/range-request utilities and TanStack Query suspense caching. - Add Parquet-derived stats (total rows / column count) and a “Download schema” action to the table viewer header.
- Add utilities + unit tests for formatting/types, column counting, and schema JSON generation; expand component tests accordingly.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/utils.ts | Adds Parquet helper utilities for type formatting, column building, column counting, and schema JSON generation. |
| src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/utils.test.ts | Adds unit tests for the new Parquet utility helpers. |
| src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/TableVisualizer.tsx | Renders optional metadata stats + “Download schema” action in the table header. |
| src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/TableVisualizer.test.tsx | Adds tests for the new header behavior and schema download action. |
| src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/ParquetVisualizer.tsx | Switches to range-request-backed Parquet reads, adds stats + schema download wiring, and uses suspense query caching. |
| src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/ParquetVisualizer.test.tsx | Updates tests to mock the async/range-request Parquet flow and validate stats/schema download behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
508c7f5 to
54eda5b
Compare
54eda5b to
17254a5
Compare
17254a5 to
cf073ca
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/IOCell.tsx:11
- IOCell now imports normalizeRawType/resolveArtifactType from ArtifactPreviewContent.tsx, which also pulls in React Query, providers, and preview components. Importing that module here just to determine whether an artifact is range-readable increases coupling and can accidentally drag heavier dependencies into the IOCell bundle. Consider extracting the artifact-type normalization/aliasing helpers into a small non-React module (e.g. ArtifactVisualizer/artifactType.ts) and importing from both IOCell and ArtifactVisualizer.
import {
normalizeRawType,
resolveArtifactType,
} from "./ArtifactVisualizer/ArtifactPreviewContent";
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/TableVisualizer.test.tsx:143
- This assertion assumes an en-US thousands separator (", "). TableVisualizer uses toLocaleString() without an explicit locale, so output depends on the test runner's default locale and can be flaky. Consider building the expected string using Intl.NumberFormat() so the test matches the runtime locale.
expect(screen.getByText("1,234,567 rows · 12 columns")).toBeInTheDocument();
cf073ca to
776d7be
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (4)
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/ParquetVisualizer.tsx:35
- Parquet FileMetaData.num_rows is a bigint (int64). Storing totalRows as a number forces later conversion (and potential precision loss for large row counts). Keeping this as bigint (or a bigint/number union) preserves correctness for large parquet files.
interface ParquetPreview {
parsed: ParsedArtifact;
totalRows: number;
columnCount: number;
schemaJson: unknown;
}
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/ParquetVisualizer.tsx:85
- Converting metadata.num_rows (bigint) to Number can lose precision for large parquet files (> 2^53-1 rows). Prefer passing through the bigint value (it supports toLocaleString) and letting the UI render it as text.
});
const totalRows = Number(metadata.num_rows);
const columnCount = countColumns(metadata);
const schemaJson = buildSchemaJson(metadata);
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/TableVisualizer.tsx:26
- TableVisualizer.totalRows is typed as number, but parquet metadata row counts are bigint (int64). With ParquetVisualizer now passing through metadata.num_rows, this prop should accept bigint to avoid precision loss.
interface TableVisualizerProps {
data: ArtifactTableData;
isFullscreen: boolean;
onLoadMore?: () => void;
onLoadAll?: () => void;
/** Total row count from file metadata (may exceed the previewed rows). */
totalRows?: number;
/** Total column count from file metadata. */
columnCount?: number;
/** When provided, renders a "Download schema" action in the stats header. */
onDownloadSchema?: () => void;
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/TableVisualizer.tsx:76
- The decorative Download icon inside the "Download schema" button is missing aria-hidden. In this codebase, icons adjacent to visible text are typically marked aria-hidden="true" to avoid screen readers announcing the icon (e.g. src/components/Onboarding/OnboardingNavPill.tsx:27, src/components/Learn/TipOfTheDay.tsx:102).
{onDownloadSchema && (
<Button variant="link" size="inline-xs" onClick={onDownloadSchema}>
<Icon name="Download" size="xs" />
Download schema
</Button>
|
Thanks @Copilot — went through the review. Addressed 1–6:
Declining the |
776d7be to
d6f38d8
Compare
d6f38d8 to
27c302e
Compare
There was a problem hiding this comment.
🟡 Not ready to approve
The new hyparquet runtime import in a shared utils.ts used by non-parquet paths can unnecessarily pull Parquet code into common previews, impacting bundle size/performance.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (1)
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/parquetUtils.test.ts:49
- This comment says the row count is a “plain large count”, but the value is
42n. It would be clearer to explain that the test uses a bigint to ensurebuildSchemaJsonsanitizes bigint values (soJSON.stringifywon’t throw).
// A bigint row count exercises hyparquet's toJson bigint sanitization.
42n,
);
- Files reviewed: 13/13 changed files
- Comments generated: 0 new
- Review effort level: Low
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
27c302e to
856dc8b
Compare
There was a problem hiding this comment.
🟡 Not ready to approve
There are confirmed error-handling edge cases that can break parquet previews (hyparquet HEAD method detection) or trigger unbounded full downloads when Content-Length is unavailable, plus “load more” failures currently don’t surface to the UI.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (3)
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/parquetUtils.ts:73
- In
openParquet's non-range fallback, ifContent-Lengthis missing/unavailable (common with some CORS setups),contentLengthbecomesNaNand the code will proceed toresponse.arrayBuffer(), potentially downloading an arbitrarily large parquet into memory. It would be safer to treat unknown length as not previewable without range support (same 413 path).
const response = await fetchArtifactOrThrow(signedUrl);
const contentLength = Number(response.headers.get("Content-Length"));
if (
Number.isFinite(contentLength) &&
contentLength > PARQUET_FULL_DOWNLOAD_LIMIT_BYTES
) {
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/useArtifactFetch.tsx:47
fetchArtifactForHyparquetinfers the HTTP method only frominit?.method, butfetchcallers can pass aRequestobject where the method lives oninput. If hyparquet uses aRequestfor itsHEADprobe, this code will mis-detect it as GET and incorrectly throw on the expectedHEAD403, breaking parquet previews.
export const fetchArtifactForHyparquet: typeof fetch = async (input, init) => {
const response = await fetch(input, init);
if (response.ok) return response;
const method = (init?.method ?? "GET").toUpperCase();
if (method === "HEAD" && response.status === 403) {
return response;
}
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/ParquetVisualizer.tsx:108
- When loading additional parquet rows, errors are only logged to the console and the UI silently stays on the previous data. This makes failures hard to notice and bypasses the existing ArtifactVisualizer error boundary behavior used for initial load failures.
const loadUpTo = async (target: number) => {
if (isLoading || target <= loadedCount) return;
setIsLoading(true);
try {
const more = await readParquetRows(
base.opened,
base.columns,
loadedCount,
target,
);
setRows((prev) => [...prev, ...more]);
} catch (error) {
console.error("Failed to load more parquet rows:", error);
} finally {
setIsLoading(false);
}
- Files reviewed: 13/13 changed files
- Comments generated: 0 new
- Review effort level: Low
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
There was a problem hiding this comment.
🟡 Not ready to approve
The Parquet fallback size-cap check can be bypassed when the Content-Length header is missing/unexposed (parsed as 0), allowing unintended large full downloads.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (1)
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/parquetUtils.ts:73
Number(response.headers.get("Content-Length"))treats a missing/blocked Content-Length header as0(becauseNumber(null) === 0). In that case the full-download fallback will proceed even for very large files, defeating the intended safety cap when the header isn't exposed via CORS or isn't present. Parse the header explicitly and treat missing/NaN as unknown so the size check doesn't silently pass.
const contentLength = Number(response.headers.get("Content-Length"));
if (
Number.isFinite(contentLength) &&
contentLength > PARQUET_FULL_DOWNLOAD_LIMIT_BYTES
) {
- Files reviewed: 13/13 changed files
- Comments generated: 0 new
- Review effort level: Low
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
be78fac to
9a8abc8
Compare
There was a problem hiding this comment.
🟡 Not ready to approve
There are confirmed correctness/security issues in the new fetch and fallback paths (HEAD method detection and Content-Length parsing, plus missing noopener/noreferrer on signed URL opens) that should be addressed before approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (3)
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/parquetUtils.ts:73
Number(response.headers.get("Content-Length"))treats a missing header (null) as0, which can incorrectly bypass the full-download size cap and trigger an unbounded download in the fallback path. Parse the header explicitly so missing/invalid values becomeNaN(and therefore skip the cap check rather than defaulting to 0).
const response = await fetchArtifactOrThrow(signedUrl);
const contentLength = Number(response.headers.get("Content-Length"));
if (
Number.isFinite(contentLength) &&
contentLength > PARQUET_FULL_DOWNLOAD_LIMIT_BYTES
) {
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/ParquetVisualizer.tsx:143
- Opening the signed artifact URL in a new tab should use
noopener,noreferrerto prevent reverse-tabnabbing and avoid leaking referrer information to the storage origin.
onDownloadFull={() => window.open(signedUrl, "_blank")}
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/useArtifactFetch.tsx:37
fetchArtifactForHyparquetonly checksinit?.methodto detectHEAD, but callers can pass aRequestobject withmethod: "HEAD"and noinit. In that case this defaults to GET and will throw on the expected 403 HEAD response, breaking hyparquet's fallback behavior for GCS signed URLs.
const method = (init?.method ?? "GET").toUpperCase();
- Files reviewed: 13/13 changed files
- Comments generated: 0 new
- Review effort level: Low
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
9a8abc8 to
0f5323e
Compare
There was a problem hiding this comment.
🟡 Not ready to approve
New window.open(..., "_blank") calls should add noopener,noreferrer for security, and the Parquet “load more” failure path currently provides no user-visible feedback.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (4)
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/ParquetVisualizer.tsx:144
window.open(..., "_blank")should includenoopener,noreferrer(tabnabbing protection). There’s already an established pattern for this insrc/components/Learn/LearnSearchBar.tsx:21using a third argument.
onDownloadSchema={handleDownloadSchema}
onDownloadFull={() => window.open(signedUrl, "_blank")}
/>
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/CsvVisualizer.tsx:80
window.open(..., "_blank")should includenoopener,noreferrerto prevent the opened page from getting a handle towindow.opener(tabnabbing). This is consistent withsrc/components/Learn/LearnSearchBar.tsx:21.
return (
<CsvContent
parsed={parsed}
isFullscreen={isFullscreen}
onDownloadFull={() => window.open(signedUrl, "_blank")}
/>
);
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/ParquetVisualizer.tsx:114
- If loading additional parquet rows fails, the error is only logged to the console; the UI keeps showing the existing rows with no user-visible feedback. Consider surfacing a small inline error message and/or a toast so users know the "Load more" action failed.
target,
);
setRows((prev) => [...prev, ...more]);
} catch (error) {
console.error("Failed to load more parquet rows:", error);
} finally {
setIsLoading(false);
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/utils.ts:47
parseCsvnow parses the entire (already-downloaded) CSV/TSV input to compute an exacttotalRows(viastep), which can noticeably block the main thread for near-limit files (up to 50MB). If the row count is mainly for UI stats, consider an approach that avoids fully parsing synchronously (e.g. Papaworker: truewhere supported) or reverting to a bounded parse and showing an approximate/unknown total when truncated.
/**
* Parse delimited text into a preview table. The whole (already-downloaded)
* text is streamed row by row so we can report the exact `totalRows` while
* retaining only the first {@link MAX_PREVIEW_ROWS} rows in memory.
*/
export function parseCsv(text: string, delimiter?: string): ParsedArtifact {
let headers: string[] | undefined;
const rows: string[][] = [];
let totalRows = 0;
Papa.parse<string[]>(text, {
delimiter,
header: false,
skipEmptyLines: true,
step: (result) => {
const row = result.data;
if (headers === undefined) {
headers = row;
return;
}
totalRows++;
if (rows.length < MAX_PREVIEW_ROWS) rows.push(row);
},
});
- Files reviewed: 17/17 changed files
- Comments generated: 0 new
- Review effort level: Low
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
0f5323e to
1633455
Compare
1633455 to
436b6ef
Compare
There was a problem hiding this comment.
🟡 Not ready to approve
There are confirmed security and operational robustness issues (window.open without noopener and fallback full-download safety gaps) that should be addressed before approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (2)
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/useArtifactFetch.tsx:41
fetchArtifactForHyparquetonly checksinit.methodto detect theHEADprobe. If aRequestobject is passed (method set on the Request, noinit), this will be treated asGETand the expected403will be thrown, preventing hyparquet’s fallback to rangedGET.
transform: (response: Response) => Promise<T>,
): T {
const { data } = useSuspenseQuery({
queryKey: [`artifact-${queryKey}`, signedUrl],
queryFn: async () => {
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/ParquetVisualizer.tsx:113
loadUpToswallows range-read failures by only logging to the console; from the UI perspective the action just does nothing. Consider surfacing an inline error state or a toast so users know the load failed and can retry.
} catch (error) {
console.error("Failed to load more parquet rows:", error);
} finally {
- Files reviewed: 17/17 changed files
- Comments generated: 3
- Review effort level: Low
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
| const response = await fetchArtifactOrThrow(signedUrl); | ||
| const contentLength = Number(response.headers.get("Content-Length")); | ||
| if ( | ||
| Number.isFinite(contentLength) && | ||
| contentLength > PARQUET_FULL_DOWNLOAD_LIMIT_BYTES | ||
| ) { | ||
| throw new ArtifactFetchError( | ||
| 413, | ||
| "Payload Too Large", | ||
| "This parquet file is too large to preview without range-request support.", | ||
| ); | ||
| } |
There was a problem hiding this comment.
🟡 Not ready to approve
There are a few correctness/safety gaps (notably parquet size-limit/type inference in IOCell and the parquet full-download fallback size guard when Content-Length is missing) that could reintroduce blocked previews or large unintended downloads.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (5)
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/IOCell.tsx:32
artifactTypeshould consider the artifact URI as a fallback (viainferTypeFromUri) so parquet artifacts without an explicittype/type_namestill get the parquet preview behavior (including bypassing the 50MB size limit).
const artifactType =
type ?? artifact?.type_name ?? (artifactData?.is_dir ? "Directory" : "Any");
const isRangeReadable =
resolveArtifactType(normalizeRawType(artifactType)) === "apacheparquet";
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/parquetUtils.ts:57
- In the non-range fallback path,
Number(response.headers.get("Content-Length"))becomes0when the header is missing (andNaNwhen unparsable), which bypasses the size cap and can still trigger a very large full download into memory. It’s safer to fail closed when the size is unknown, since this code path only exists when range requests are unavailable.
const response = await fetchArtifactOrThrow(signedUrl);
const contentLength = Number(response.headers.get("Content-Length"));
if (
Number.isFinite(contentLength) &&
contentLength > PARQUET_FULL_DOWNLOAD_LIMIT_BYTES
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/IOCell.tsx:11
IOCell's parquet size-limit exemption relies onartifactType, butartifactTypecan fall back to "Any" whentype/type_nameare missing. In that case a.parquetURI would still be treated as non-range-readable and the preview may be incorrectly hidden for large parquet artifacts. Importing and usinginferTypeFromUrienables consistent type inference from the URI.
This issue also appears on line 28 of the same file.
import {
normalizeRawType,
resolveArtifactType,
} from "./ArtifactVisualizer/artifactType";
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/CsvVisualizer.tsx:59
CsvVisualizerValueis used for both CSV and TSV inline previews (seeInlineContent), but the full-dataset download always saves asdata.csvwith atext/csvcontent-type. This produces the wrong extension/MIME for TSV inline values.
<CsvContent
parsed={parseCsv(value)}
isFullscreen={isFullscreen}
onDownloadFull={() =>
downloadStringAsFile(value, "data.csv", "text/csv;charset=utf-8")
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/utils.ts:33
parseCsvnow iterates the entire input viastepin order to compute an exacttotalRows. Previously the parser used Papa'spreviewoption to cap work; this change can significantly slow down rendering for large (but still <= 50MB) CSV/TSV artifacts because it must parse the whole file before showing the first rows.
Papa.parse<string[]>(text, {
delimiter,
header: false,
skipEmptyLines: true,
step: (result) => {
- Files reviewed: 17/17 changed files
- Comments generated: 0 new
- Review effort level: Low
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
436b6ef to
c472ea6
Compare
There was a problem hiding this comment.
🟡 Not ready to approve
There are confirmed edge-case bugs that can incorrectly hide Parquet previews (missing type metadata) and bypass the fallback full-download size guard when Content-Length is absent.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (2)
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/parquetUtils.ts:56
Number(response.headers.get("Content-Length"))treats a missing header as0(becauseNumber(null) === 0), which bypasses the >50MB safety check and can lead to unexpectedly full-downloading very large parquet files when range requests fail. Parse the header defensively sonullbecomesNaN(or otherwise handled) instead of0.
const response = await fetchArtifactOrThrow(signedUrl);
const contentLength = Number(response.headers.get("Content-Length"));
if (
Number.isFinite(contentLength) &&
contentLength > PARQUET_FULL_DOWNLOAD_LIMIT_BYTES
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/IOCell.tsx:32
isRangeReadableis computed only from the declaredtype/type_name. If those are missing but the URI ends in.parquet, large parquet artifacts will still be treated as non-range-readable and the 50MB preview limit will hide the preview button. Consider inferring the type fromartifactData.urias a fallback for this check.
const artifactType =
type ?? artifact?.type_name ?? (artifactData?.is_dir ? "Directory" : "Any");
const isRangeReadable =
resolveArtifactType(normalizeRawType(artifactType)) === "apacheparquet";
- Files reviewed: 13/13 changed files
- Comments generated: 0 new
- Review effort level: Low
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
c472ea6 to
d3eb937
Compare
There was a problem hiding this comment.
🟡 Not ready to approve
The Parquet fallback size check can be bypassed when Content-Length is missing because Number(null) becomes 0, enabling unintended full-file downloads.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Comments suppressed due to low confidence (1)
src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/ArtifactVisualizer/parquetUtils.ts:55
Number(response.headers.get("Content-Length"))coerces a missing header (null) to0, so if the server omitsContent-Lengththis fallback path will treat the file as size 0 and proceed toarrayBuffer()download the entire parquet into memory—bypassing the intended 50MB safety cap.
const response = await fetchArtifactOrThrow(signedUrl);
const contentLength = Number(response.headers.get("Content-Length"));
if (
Number.isFinite(contentLength) &&
contentLength > MAX_VISUALIZABLE_SIZE_BYTES
) {
- Files reviewed: 14/14 changed files
- Comments generated: 0 new
- Review effort level: Low
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

What this does
Improves how Parquet files are previewed in the artifact viewer.
Before: opening a Parquet preview downloaded the whole file and then showed up to 1,000 rows. Big files were slow, and files above the preview size limit couldn't be opened at all.
Now: the viewer reads the file in pages — it only pulls the parts it actually needs (the file's metadata plus the first rows) instead of downloading everything up front. In practice this means:
Type of Change
Test Instructions
Notes
Also includes some internal cleanup raised in review: Parquet-only code was moved into its own module so non-Parquet previews (CSV/TSV) stay lightweight, and the fetch/error-handling logic that was duplicated is now shared.
Follow-up
Additional viewer affordances — incremental Load more / Load max, a Download full dataset escape hatch, and CSV/TSV row/column-count parity — are stacked on top in #2593.