Fix(excel-range): fixed excel range (#1088)

* added auto range

* lint

* removed any

* utils file

---------

Co-authored-by: Adam Gough <adamgough@Mac.attlocal.net>
This commit is contained in:
Adam Gough
2025-08-21 20:04:20 -07:00
committed by GitHub
parent 9dbd44e555
commit 77b0c5b9ed
2 changed files with 52 additions and 7 deletions

View File

@@ -1,7 +1,9 @@
import type {
ExcelCellValue,
MicrosoftExcelReadResponse,
MicrosoftExcelToolParams,
} from '@/tools/microsoft_excel/types'
import { trimTrailingEmptyRowsAndColumns } from '@/tools/microsoft_excel/utils'
import type { ToolConfig } from '@/tools/types'
export const readTool: ToolConfig<MicrosoftExcelToolParams, MicrosoftExcelReadResponse> = {
@@ -75,8 +77,6 @@ export const readTool: ToolConfig<MicrosoftExcelToolParams, MicrosoftExcelReadRe
},
transformResponse: async (response: Response, params?: MicrosoftExcelToolParams) => {
const defaultAddress = 'A1:Z1000' // Match Google Sheets default logic
// If we came from the worksheets listing (no range provided), resolve first sheet name then fetch range
if (response.url.includes('/workbook/worksheets?')) {
const listData = await response.json()
@@ -92,9 +92,10 @@ export const readTool: ToolConfig<MicrosoftExcelToolParams, MicrosoftExcelReadRe
throw new Error('Access token is required to read Excel range')
}
// Use usedRange(valuesOnly=true) to fetch only populated cells, avoiding thousands of empty rows
const rangeUrl = `https://graph.microsoft.com/v1.0/me/drive/items/${encodeURIComponent(
spreadsheetIdFromUrl
)}/workbook/worksheets('${encodeURIComponent(firstSheetName)}')/range(address='${defaultAddress}')`
)}/workbook/worksheets('${encodeURIComponent(firstSheetName)}')/usedRange(valuesOnly=true)`
const rangeResp = await fetch(rangeUrl, {
headers: { Authorization: `Bearer ${accessToken}` },
@@ -109,6 +110,12 @@ export const readTool: ToolConfig<MicrosoftExcelToolParams, MicrosoftExcelReadRe
const data = await rangeResp.json()
// usedRange returns an address (A1 notation) and values matrix
const address: string = data.address || data.addressLocal || `${firstSheetName}!A1`
const rawValues: ExcelCellValue[][] = data.values || []
const values = trimTrailingEmptyRowsAndColumns(rawValues)
const metadata = {
spreadsheetId: spreadsheetIdFromUrl,
properties: {},
@@ -119,8 +126,8 @@ export const readTool: ToolConfig<MicrosoftExcelToolParams, MicrosoftExcelReadRe
success: true,
output: {
data: {
range: data.range || `${firstSheetName}!${defaultAddress}`,
values: data.values || [],
range: address,
values,
},
metadata: {
spreadsheetId: metadata.spreadsheetId,
@@ -144,12 +151,16 @@ export const readTool: ToolConfig<MicrosoftExcelToolParams, MicrosoftExcelReadRe
spreadsheetUrl: `https://graph.microsoft.com/v1.0/me/drive/items/${spreadsheetId}`,
}
const address: string = data.address || data.addressLocal || data.range || ''
const rawValues: ExcelCellValue[][] = data.values || []
const values = trimTrailingEmptyRowsAndColumns(rawValues)
const result: MicrosoftExcelReadResponse = {
success: true,
output: {
data: {
range: data.range || '',
values: data.values || [],
range: address,
values,
},
metadata: {
spreadsheetId: metadata.spreadsheetId,

View File

@@ -0,0 +1,34 @@
import type { ExcelCellValue } from '@/tools/microsoft_excel/types'
export function trimTrailingEmptyRowsAndColumns(matrix: ExcelCellValue[][]): ExcelCellValue[][] {
if (!Array.isArray(matrix) || matrix.length === 0) return []
const isEmptyValue = (v: ExcelCellValue) => v === null || v === ''
// Determine last non-empty row
let lastNonEmptyRowIndex = -1
for (let r = 0; r < matrix.length; r++) {
const row = matrix[r] || []
const hasData = row.some((cell: ExcelCellValue) => !isEmptyValue(cell))
if (hasData) lastNonEmptyRowIndex = r
}
if (lastNonEmptyRowIndex === -1) return []
const trimmedRows = matrix.slice(0, lastNonEmptyRowIndex + 1)
// Determine last non-empty column across trimmed rows
let lastNonEmptyColIndex = -1
for (let r = 0; r < trimmedRows.length; r++) {
const row = trimmedRows[r] || []
for (let c = 0; c < row.length; c++) {
if (!isEmptyValue(row[c])) {
if (c > lastNonEmptyColIndex) lastNonEmptyColIndex = c
}
}
}
if (lastNonEmptyColIndex === -1) return []
return trimmedRows.map((row) => (row || []).slice(0, lastNonEmptyColIndex + 1))
}