@@ -18,7 +18,6 @@ interface GoogleSheetsWebhookConfig {
1818 manualSpreadsheetId ?: string
1919 sheetName ?: string
2020 manualSheetName ?: string
21- includeHeaders : boolean
2221 valueRenderOption ?: ValueRenderOption
2322 dateTimeRenderOption ?: DateTimeRenderOption
2423 lastKnownRowCount ?: number
@@ -147,19 +146,15 @@ export const googleSheetsPollingHandler: PollingProviderHandler = {
147146 const valueRender = config . valueRenderOption || 'FORMATTED_VALUE'
148147 const dateTimeRender = config . dateTimeRenderOption || 'SERIAL_NUMBER'
149148
150- // Fetch headers (row 1) if includeHeaders is enabled
151- let headers : string [ ] = [ ]
152- if ( config . includeHeaders !== false ) {
153- headers = await fetchHeaderRow (
154- accessToken ,
155- spreadsheetId ,
156- sheetName ,
157- valueRender ,
158- dateTimeRender ,
159- requestId ,
160- logger
161- )
162- }
149+ const headers = await fetchHeaderRow (
150+ accessToken ,
151+ spreadsheetId ,
152+ sheetName ,
153+ valueRender ,
154+ dateTimeRender ,
155+ requestId ,
156+ logger
157+ )
163158
164159 // Fetch new rows — startRow/endRow are already 1-indexed sheet row numbers
165160 // because lastKnownRowCount includes the header row
@@ -269,7 +264,12 @@ async function getDataRowCount(
269264 logger : ReturnType < typeof import ( '@sim/logger' ) . createLogger >
270265) : Promise < number > {
271266 const encodedSheet = encodeURIComponent ( sheetName )
272- const url = `https://sheets.googleapis.com/v4/spreadsheets/${ spreadsheetId } /values/${ encodedSheet } !A:A?majorDimension=COLUMNS&fields=values`
267+ // Fetch all rows across columns A–Z with majorDimension=ROWS so the API
268+ // returns one entry per row that has ANY non-empty cell. Rows where column A
269+ // is empty but other columns have data are included, whereas the previous
270+ // column-A-only approach silently missed them. The returned array length
271+ // equals the 1-indexed row number of the last row with data.
272+ const url = `https://sheets.googleapis.com/v4/spreadsheets/${ spreadsheetId } /values/${ encodedSheet } !A:Z?majorDimension=ROWS&fields=values`
273273
274274 const response = await fetch ( url , {
275275 headers : { Authorization : `Bearer ${ accessToken } ` } ,
@@ -291,9 +291,11 @@ async function getDataRowCount(
291291 }
292292
293293 const data = await response . json ( )
294- // values is [[cell1, cell2, ...]] when majorDimension=COLUMNS
295- const columnValues = data . values ?. [ 0 ] as string [ ] | undefined
296- return columnValues ?. length ?? 0
294+ // values is [[row1col1, row1col2, ...], [row2col1, ...], ...] when majorDimension=ROWS.
295+ // The Sheets API omits trailing empty rows, so the array length is the last
296+ // non-empty row index (1-indexed), which is exactly what we need.
297+ const rows = data . values as string [ ] [ ] | undefined
298+ return rows ?. length ?? 0
297299}
298300
299301async function fetchHeaderRow (
@@ -399,15 +401,12 @@ async function processRows(
399401 'google-sheets' ,
400402 `${ webhookData . id } :${ spreadsheetId } :${ sheetName } :row${ rowNumber } ` ,
401403 async ( ) => {
402- // Map row values to headers
403404 let mappedRow : Record < string , string > | null = null
404- if ( headers . length > 0 && config . includeHeaders !== false ) {
405+ if ( headers . length > 0 ) {
405406 mappedRow = { }
406407 for ( let j = 0 ; j < headers . length ; j ++ ) {
407- const header = headers [ j ] || `Column ${ j + 1 } `
408- mappedRow [ header ] = row [ j ] ?? ''
408+ mappedRow [ headers [ j ] || `Column ${ j + 1 } ` ] = row [ j ] ?? ''
409409 }
410- // Include any extra columns beyond headers
411410 for ( let j = headers . length ; j < row . length ; j ++ ) {
412411 mappedRow [ `Column ${ j + 1 } ` ] = row [ j ] ?? ''
413412 }
0 commit comments