Skip to content

Commit 5ee6345

Browse files
authored
Fix(google drive): google sheets creating a file (#1476)
* fixed upload to google drive with sheets * cleanup, remove unneeded scope
1 parent dd1985c commit 5ee6345

File tree

2 files changed

+95
-6
lines changed

2 files changed

+95
-6
lines changed

apps/sim/tools/google_drive/upload.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { createLogger } from '@/lib/logs/console/logger'
22
import type { GoogleDriveToolParams, GoogleDriveUploadResponse } from '@/tools/google_drive/types'
3-
import { GOOGLE_WORKSPACE_MIME_TYPES, SOURCE_MIME_TYPES } from '@/tools/google_drive/utils'
3+
import {
4+
GOOGLE_WORKSPACE_MIME_TYPES,
5+
handleSheetsFormat,
6+
SOURCE_MIME_TYPES,
7+
} from '@/tools/google_drive/utils'
48
import type { ToolConfig } from '@/tools/types'
59

610
const logger = createLogger('GoogleDriveUploadTool')
@@ -96,13 +100,27 @@ export const uploadTool: ToolConfig<GoogleDriveToolParams, GoogleDriveUploadResp
96100
throw new Error(data.error?.message || 'Failed to create file in Google Drive')
97101
}
98102

99-
// Now upload content to the created file
100103
const fileId = data.id
101104
const requestedMimeType = params?.mimeType || 'text/plain'
102105
const authHeader =
103106
response.headers.get('Authorization') || `Bearer ${params?.accessToken || ''}`
104107

105-
// For Google Workspace formats, use the appropriate source MIME type for content upload
108+
let preparedContent: string | undefined =
109+
typeof params?.content === 'string' ? (params?.content as string) : undefined
110+
111+
if (requestedMimeType === 'application/vnd.google-apps.spreadsheet' && params?.content) {
112+
const { csv, rowCount, columnCount } = handleSheetsFormat(params.content as unknown)
113+
if (csv !== undefined) {
114+
preparedContent = csv
115+
logger.info('Prepared CSV content for Google Sheets upload', {
116+
fileId,
117+
fileName: params?.fileName,
118+
rowCount,
119+
columnCount,
120+
})
121+
}
122+
}
123+
106124
const uploadMimeType = GOOGLE_WORKSPACE_MIME_TYPES.includes(requestedMimeType)
107125
? SOURCE_MIME_TYPES[requestedMimeType] || 'text/plain'
108126
: requestedMimeType
@@ -122,7 +140,7 @@ export const uploadTool: ToolConfig<GoogleDriveToolParams, GoogleDriveUploadResp
122140
Authorization: authHeader,
123141
'Content-Type': uploadMimeType,
124142
},
125-
body: params?.content || '',
143+
body: preparedContent !== undefined ? preparedContent : params?.content || '',
126144
}
127145
)
128146

@@ -136,7 +154,6 @@ export const uploadTool: ToolConfig<GoogleDriveToolParams, GoogleDriveUploadResp
136154
throw new Error(uploadError.error?.message || 'Failed to upload content to file')
137155
}
138156

139-
// For Google Workspace documents, update the name again to ensure it sticks after conversion
140157
if (GOOGLE_WORKSPACE_MIME_TYPES.includes(requestedMimeType)) {
141158
logger.info('Updating file name to ensure it persists after conversion', {
142159
fileId,
@@ -165,7 +182,6 @@ export const uploadTool: ToolConfig<GoogleDriveToolParams, GoogleDriveUploadResp
165182
}
166183
}
167184

168-
// Get the final file data
169185
const finalFileResponse = await fetch(
170186
`https://www.googleapis.com/drive/v3/files/${fileId}?supportsAllDrives=true&fields=id,name,mimeType,webViewLink,webContentLink,size,createdTime,modifiedTime,parents`,
171187
{

apps/sim/tools/google_drive/utils.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,76 @@ export const SOURCE_MIME_TYPES: Record<string, string> = {
2121
'application/vnd.google-apps.spreadsheet': 'text/csv',
2222
'application/vnd.google-apps.presentation': 'application/vnd.ms-powerpoint',
2323
}
24+
25+
export function handleSheetsFormat(input: unknown): {
26+
csv?: string
27+
rowCount: number
28+
columnCount: number
29+
} {
30+
let workingValue: unknown = input
31+
32+
if (typeof workingValue === 'string') {
33+
try {
34+
workingValue = JSON.parse(workingValue)
35+
} catch (_error) {
36+
const csvString = workingValue as string
37+
return { csv: csvString, rowCount: 0, columnCount: 0 }
38+
}
39+
}
40+
41+
if (!Array.isArray(workingValue)) {
42+
return { rowCount: 0, columnCount: 0 }
43+
}
44+
45+
let table: unknown[] = workingValue
46+
47+
if (
48+
table.length > 0 &&
49+
typeof (table as any)[0] === 'object' &&
50+
(table as any)[0] !== null &&
51+
!Array.isArray((table as any)[0])
52+
) {
53+
const allKeys = new Set<string>()
54+
;(table as any[]).forEach((obj) => {
55+
if (obj && typeof obj === 'object') {
56+
Object.keys(obj).forEach((key) => allKeys.add(key))
57+
}
58+
})
59+
const headers = Array.from(allKeys)
60+
const rows = (table as any[]).map((obj) => {
61+
if (!obj || typeof obj !== 'object') {
62+
return Array(headers.length).fill('')
63+
}
64+
return headers.map((key) => {
65+
const value = (obj as Record<string, unknown>)[key]
66+
if (value !== null && typeof value === 'object') {
67+
return JSON.stringify(value)
68+
}
69+
return value === undefined ? '' : (value as any)
70+
})
71+
})
72+
table = [headers, ...rows]
73+
}
74+
75+
const escapeCell = (cell: unknown): string => {
76+
if (cell === null || cell === undefined) return ''
77+
const stringValue = String(cell)
78+
const mustQuote = /[",\n\r]/.test(stringValue)
79+
const doubledQuotes = stringValue.replace(/"/g, '""')
80+
return mustQuote ? `"${doubledQuotes}"` : doubledQuotes
81+
}
82+
83+
const rowsAsStrings = (table as unknown[]).map((row) => {
84+
if (!Array.isArray(row)) {
85+
return escapeCell(row)
86+
}
87+
return row.map((cell) => escapeCell(cell)).join(',')
88+
})
89+
90+
const csv = rowsAsStrings.join('\r\n')
91+
const rowCount = Array.isArray(table) ? (table as any[]).length : 0
92+
const columnCount =
93+
Array.isArray(table) && Array.isArray((table as any[])[0]) ? (table as any[])[0].length : 0
94+
95+
return { csv, rowCount, columnCount }
96+
}

0 commit comments

Comments
 (0)