Skip to content

Commit 77b0c5b

Browse files
aadamgoughAdam Gough
andauthored
Fix(excel-range): fixed excel range (#1088)
* added auto range * lint * removed any * utils file --------- Co-authored-by: Adam Gough <[email protected]>
1 parent 9dbd44e commit 77b0c5b

File tree

2 files changed

+52
-7
lines changed

2 files changed

+52
-7
lines changed

apps/sim/tools/microsoft_excel/read.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import type {
2+
ExcelCellValue,
23
MicrosoftExcelReadResponse,
34
MicrosoftExcelToolParams,
45
} from '@/tools/microsoft_excel/types'
6+
import { trimTrailingEmptyRowsAndColumns } from '@/tools/microsoft_excel/utils'
57
import type { ToolConfig } from '@/tools/types'
68

79
export const readTool: ToolConfig<MicrosoftExcelToolParams, MicrosoftExcelReadResponse> = {
@@ -75,8 +77,6 @@ export const readTool: ToolConfig<MicrosoftExcelToolParams, MicrosoftExcelReadRe
7577
},
7678

7779
transformResponse: async (response: Response, params?: MicrosoftExcelToolParams) => {
78-
const defaultAddress = 'A1:Z1000' // Match Google Sheets default logic
79-
8080
// If we came from the worksheets listing (no range provided), resolve first sheet name then fetch range
8181
if (response.url.includes('/workbook/worksheets?')) {
8282
const listData = await response.json()
@@ -92,9 +92,10 @@ export const readTool: ToolConfig<MicrosoftExcelToolParams, MicrosoftExcelReadRe
9292
throw new Error('Access token is required to read Excel range')
9393
}
9494

95+
// Use usedRange(valuesOnly=true) to fetch only populated cells, avoiding thousands of empty rows
9596
const rangeUrl = `https://graph.microsoft.com/v1.0/me/drive/items/${encodeURIComponent(
9697
spreadsheetIdFromUrl
97-
)}/workbook/worksheets('${encodeURIComponent(firstSheetName)}')/range(address='${defaultAddress}')`
98+
)}/workbook/worksheets('${encodeURIComponent(firstSheetName)}')/usedRange(valuesOnly=true)`
9899

99100
const rangeResp = await fetch(rangeUrl, {
100101
headers: { Authorization: `Bearer ${accessToken}` },
@@ -109,6 +110,12 @@ export const readTool: ToolConfig<MicrosoftExcelToolParams, MicrosoftExcelReadRe
109110

110111
const data = await rangeResp.json()
111112

113+
// usedRange returns an address (A1 notation) and values matrix
114+
const address: string = data.address || data.addressLocal || `${firstSheetName}!A1`
115+
const rawValues: ExcelCellValue[][] = data.values || []
116+
117+
const values = trimTrailingEmptyRowsAndColumns(rawValues)
118+
112119
const metadata = {
113120
spreadsheetId: spreadsheetIdFromUrl,
114121
properties: {},
@@ -119,8 +126,8 @@ export const readTool: ToolConfig<MicrosoftExcelToolParams, MicrosoftExcelReadRe
119126
success: true,
120127
output: {
121128
data: {
122-
range: data.range || `${firstSheetName}!${defaultAddress}`,
123-
values: data.values || [],
129+
range: address,
130+
values,
124131
},
125132
metadata: {
126133
spreadsheetId: metadata.spreadsheetId,
@@ -144,12 +151,16 @@ export const readTool: ToolConfig<MicrosoftExcelToolParams, MicrosoftExcelReadRe
144151
spreadsheetUrl: `https://graph.microsoft.com/v1.0/me/drive/items/${spreadsheetId}`,
145152
}
146153

154+
const address: string = data.address || data.addressLocal || data.range || ''
155+
const rawValues: ExcelCellValue[][] = data.values || []
156+
const values = trimTrailingEmptyRowsAndColumns(rawValues)
157+
147158
const result: MicrosoftExcelReadResponse = {
148159
success: true,
149160
output: {
150161
data: {
151-
range: data.range || '',
152-
values: data.values || [],
162+
range: address,
163+
values,
153164
},
154165
metadata: {
155166
spreadsheetId: metadata.spreadsheetId,
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { ExcelCellValue } from '@/tools/microsoft_excel/types'
2+
3+
export function trimTrailingEmptyRowsAndColumns(matrix: ExcelCellValue[][]): ExcelCellValue[][] {
4+
if (!Array.isArray(matrix) || matrix.length === 0) return []
5+
6+
const isEmptyValue = (v: ExcelCellValue) => v === null || v === ''
7+
8+
// Determine last non-empty row
9+
let lastNonEmptyRowIndex = -1
10+
for (let r = 0; r < matrix.length; r++) {
11+
const row = matrix[r] || []
12+
const hasData = row.some((cell: ExcelCellValue) => !isEmptyValue(cell))
13+
if (hasData) lastNonEmptyRowIndex = r
14+
}
15+
16+
if (lastNonEmptyRowIndex === -1) return []
17+
18+
const trimmedRows = matrix.slice(0, lastNonEmptyRowIndex + 1)
19+
20+
// Determine last non-empty column across trimmed rows
21+
let lastNonEmptyColIndex = -1
22+
for (let r = 0; r < trimmedRows.length; r++) {
23+
const row = trimmedRows[r] || []
24+
for (let c = 0; c < row.length; c++) {
25+
if (!isEmptyValue(row[c])) {
26+
if (c > lastNonEmptyColIndex) lastNonEmptyColIndex = c
27+
}
28+
}
29+
}
30+
31+
if (lastNonEmptyColIndex === -1) return []
32+
33+
return trimmedRows.map((row) => (row || []).slice(0, lastNonEmptyColIndex + 1))
34+
}

0 commit comments

Comments
 (0)