Skip to content

Commit 8bc96e7

Browse files
committed
fix(files): bound wide spreadsheet previews
1 parent ac7408c commit 8bc96e7

3 files changed

Lines changed: 45 additions & 10 deletions

File tree

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/xlsx-preview-data.test.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'
55
import * as XLSX from 'xlsx'
66
import {
77
readXlsxPreviewData,
8+
XLSX_MAX_COLUMNS,
89
XLSX_MAX_ROWS,
910
} from '@/app/workspace/[workspaceId]/files/components/file-viewer/xlsx-preview-data'
1011

@@ -34,7 +35,8 @@ describe('readXlsxPreviewData', () => {
3435
['row-1-a', 'row-1-b'],
3536
['row-2-a', 'row-2-b'],
3637
])
37-
expect(result.truncated).toBe(true)
38+
expect(result.rowTruncated).toBe(true)
39+
expect(result.columnTruncated).toBe(false)
3840
})
3941

4042
it('does not mark a sheet at the existing display boundary as truncated', () => {
@@ -46,6 +48,27 @@ describe('readXlsxPreviewData', () => {
4648
const result = readXlsxPreviewData(XLSX, sheet)
4749

4850
expect(result.rows).toHaveLength(XLSX_MAX_ROWS)
49-
expect(result.truncated).toBe(false)
51+
expect(result.rowTruncated).toBe(false)
52+
expect(result.columnTruncated).toBe(false)
53+
})
54+
55+
it('bounds conversion for extremely wide declared ranges', () => {
56+
const sheet = XLSX.utils.aoa_to_sheet([
57+
['header-a', 'header-b'],
58+
['row-1-a', 'row-1-b'],
59+
])
60+
sheet['!ref'] = 'A1:XFD2'
61+
const toJson = vi.spyOn(XLSX.utils, 'sheet_to_json')
62+
63+
const result = readXlsxPreviewData(XLSX, sheet)
64+
const options = toJson.mock.calls[0][1] as {
65+
range: { s: { c: number }; e: { c: number } }
66+
}
67+
68+
expect(options.range.e.c - options.range.s.c + 1).toBe(XLSX_MAX_COLUMNS)
69+
expect(result.headers).toEqual(['header-a', 'header-b'])
70+
expect(result.rows).toEqual([['row-1-a', 'row-1-b']])
71+
expect(result.rowTruncated).toBe(false)
72+
expect(result.columnTruncated).toBe(true)
5073
})
5174
})
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { WorkSheet } from 'xlsx'
22

33
export const XLSX_MAX_ROWS = 1_000
4+
export const XLSX_MAX_COLUMNS = 200
45

56
interface XlsxModule {
67
utils: Pick<typeof import('xlsx').utils, 'decode_range' | 'sheet_to_json'>
@@ -9,23 +10,26 @@ interface XlsxModule {
910
interface XlsxPreviewData {
1011
headers: string[]
1112
rows: string[][]
12-
truncated: boolean
13+
rowTruncated: boolean
14+
columnTruncated: boolean
1315
}
1416

1517
export function readXlsxPreviewData(XLSX: XlsxModule, sheet: WorkSheet): XlsxPreviewData {
1618
const declaredRange = XLSX.utils.decode_range(sheet['!ref'] || 'A1')
1719
const lastPreviewRow = Math.min(declaredRange.e.r, declaredRange.s.r + XLSX_MAX_ROWS)
20+
const lastPreviewColumn = Math.min(declaredRange.e.c, declaredRange.s.c + XLSX_MAX_COLUMNS - 1)
1821
const previewRows = XLSX.utils.sheet_to_json<string[]>(sheet, {
1922
header: 1,
2023
range: {
2124
s: declaredRange.s,
22-
e: { r: lastPreviewRow, c: declaredRange.e.c },
25+
e: { r: lastPreviewRow, c: lastPreviewColumn },
2326
},
2427
})
2528

2629
return {
2730
headers: previewRows[0] ?? [],
2831
rows: previewRows.slice(1),
29-
truncated: declaredRange.e.r > lastPreviewRow,
32+
rowTruncated: declaredRange.e.r > lastPreviewRow,
33+
columnTruncated: declaredRange.e.c > lastPreviewColumn,
3034
}
3135
}

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/xlsx-preview.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { WorkspaceFileRecord } from '@/lib/uploads/contexts/workspace'
1010
import { useHorizontalWheelScroll } from '@/app/workspace/[workspaceId]/files/components/file-viewer/use-horizontal-wheel-scroll'
1111
import {
1212
readXlsxPreviewData,
13+
XLSX_MAX_COLUMNS,
1314
XLSX_MAX_ROWS,
1415
} from '@/app/workspace/[workspaceId]/files/components/file-viewer/xlsx-preview-data'
1516
import { DataTable } from './data-table'
@@ -22,7 +23,8 @@ interface XlsxSheet {
2223
name: string
2324
headers: string[]
2425
rows: string[][]
25-
truncated: boolean
26+
rowTruncated: boolean
27+
columnTruncated: boolean
2628
}
2729

2830
export const XlsxPreview = memo(function XlsxPreview({
@@ -85,13 +87,14 @@ export const XlsxPreview = memo(function XlsxPreview({
8587
const workbook = workbookRef.current!
8688
const name = sheetNames[activeSheet]
8789
const sheet = workbook.Sheets[name]
88-
const { headers, rows, truncated } = readXlsxPreviewData(XLSX, sheet)
90+
const { headers, rows, rowTruncated, columnTruncated } = readXlsxPreviewData(XLSX, sheet)
8991
if (!cancelled) {
9092
setCurrentSheet({
9193
name,
9294
headers,
9395
rows,
94-
truncated,
96+
rowTruncated,
97+
columnTruncated,
9598
})
9699
}
97100
} catch (err) {
@@ -133,9 +136,14 @@ export const XlsxPreview = memo(function XlsxPreview({
133136
</div>
134137
<div ref={scrollRef} className='flex-1 overflow-auto p-6'>
135138
<DataTable headers={currentSheet.headers} rows={currentSheet.rows} />
136-
{currentSheet.truncated && (
139+
{(currentSheet.rowTruncated || currentSheet.columnTruncated) && (
137140
<p className='mt-3 text-center text-[12px] text-[var(--text-muted)]'>
138-
Showing first {XLSX_MAX_ROWS.toLocaleString()} rows. Download the file to view all data.
141+
{currentSheet.rowTruncated && currentSheet.columnTruncated
142+
? `Showing first ${XLSX_MAX_ROWS.toLocaleString()} rows and ${XLSX_MAX_COLUMNS.toLocaleString()} columns.`
143+
: currentSheet.rowTruncated
144+
? `Showing first ${XLSX_MAX_ROWS.toLocaleString()} rows.`
145+
: `Showing first ${XLSX_MAX_COLUMNS.toLocaleString()} columns.`}{' '}
146+
Download the file to view all data.
139147
</p>
140148
)}
141149
</div>

0 commit comments

Comments
 (0)