Skip to content

Commit 785f847

Browse files
fix(onedrive): parse array values correctly (#1981)
* fix(onedrive): parse array values correctly * fix onedrive * fix * fix onedrive input parsing by reusing code subblock * fix type
1 parent dab70a8 commit 785f847

File tree

7 files changed

+117
-25
lines changed

7 files changed

+117
-25
lines changed

apps/sim/app/api/tools/onedrive/upload/route.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,30 @@ import { createLogger } from '@/lib/logs/console/logger'
66
import { processSingleFileToUserFile } from '@/lib/uploads/utils/file-utils'
77
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
88
import { generateRequestId } from '@/lib/utils'
9+
import { normalizeExcelValues } from '@/tools/onedrive/utils'
910

1011
export const dynamic = 'force-dynamic'
1112

1213
const logger = createLogger('OneDriveUploadAPI')
1314

1415
const MICROSOFT_GRAPH_BASE = 'https://graph.microsoft.com/v1.0'
1516

17+
const ExcelCellSchema = z.union([z.string(), z.number(), z.boolean(), z.null()])
18+
const ExcelRowSchema = z.array(ExcelCellSchema)
19+
const ExcelValuesSchema = z.union([
20+
z.string(),
21+
z.array(ExcelRowSchema),
22+
z.array(z.record(ExcelCellSchema)),
23+
])
24+
1625
const OneDriveUploadSchema = z.object({
1726
accessToken: z.string().min(1, 'Access token is required'),
1827
fileName: z.string().min(1, 'File name is required'),
1928
file: z.any().optional(), // UserFile object (optional for blank Excel creation)
2029
folderId: z.string().optional().nullable(),
2130
mimeType: z.string().optional(),
2231
// Optional Excel write-after-create inputs
23-
values: z.array(z.array(z.union([z.string(), z.number(), z.boolean(), z.null()]))).optional(),
32+
values: ExcelValuesSchema.optional(),
2433
})
2534

2635
export async function POST(request: NextRequest) {
@@ -46,6 +55,7 @@ export async function POST(request: NextRequest) {
4655

4756
const body = await request.json()
4857
const validatedData = OneDriveUploadSchema.parse(body)
58+
const excelValues = normalizeExcelValues(validatedData.values)
4959

5060
let fileBuffer: Buffer
5161
let mimeType: string
@@ -180,7 +190,7 @@ export async function POST(request: NextRequest) {
180190
// If this is an Excel creation and values were provided, write them using the Excel API
181191
let excelWriteResult: any | undefined
182192
const shouldWriteExcelContent =
183-
isExcelCreation && Array.isArray(validatedData.values) && validatedData.values.length > 0
193+
isExcelCreation && Array.isArray(excelValues) && excelValues.length > 0
184194

185195
if (shouldWriteExcelContent) {
186196
try {
@@ -232,7 +242,7 @@ export async function POST(request: NextRequest) {
232242
logger.warn(`[${requestId}] Error listing worksheets, using default Sheet1`, listError)
233243
}
234244

235-
let processedValues: any = validatedData.values || []
245+
let processedValues: any = excelValues || []
236246

237247
if (
238248
Array.isArray(processedValues) &&

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/code/code.tsx

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -221,17 +221,26 @@ export function Code({
221221
// Derived state
222222
const effectiveLanguage = (languageValue as 'javascript' | 'python' | 'json') || language
223223

224+
const trimmedCode = code.trim()
225+
const containsReferencePlaceholders =
226+
trimmedCode.includes('{{') ||
227+
trimmedCode.includes('}}') ||
228+
trimmedCode.includes('<') ||
229+
trimmedCode.includes('>')
230+
231+
const shouldValidateJson = effectiveLanguage === 'json' && !containsReferencePlaceholders
232+
224233
const isValidJson = useMemo(() => {
225-
if (subBlockId !== 'responseFormat' || !code.trim()) {
234+
if (!shouldValidateJson || !trimmedCode) {
226235
return true
227236
}
228237
try {
229-
JSON.parse(code)
238+
JSON.parse(trimmedCode)
230239
return true
231240
} catch {
232241
return false
233242
}
234-
}, [subBlockId, code])
243+
}, [shouldValidateJson, trimmedCode])
235244

236245
const gutterWidthPx = useMemo(() => {
237246
const lineCount = code.split('\n').length
@@ -309,14 +318,29 @@ export function Code({
309318
: storeValue
310319

311320
// Effects: JSON validation
321+
const lastValidationStatus = useRef<boolean>(true)
322+
312323
useEffect(() => {
313-
if (onValidationChange && subBlockId === 'responseFormat') {
314-
const timeoutId = setTimeout(() => {
315-
onValidationChange(isValidJson)
316-
}, 150)
317-
return () => clearTimeout(timeoutId)
324+
if (!onValidationChange) return
325+
326+
const nextStatus = shouldValidateJson ? isValidJson : true
327+
if (lastValidationStatus.current === nextStatus) {
328+
return
318329
}
319-
}, [isValidJson, onValidationChange, subBlockId])
330+
331+
lastValidationStatus.current = nextStatus
332+
333+
if (!shouldValidateJson) {
334+
onValidationChange(nextStatus)
335+
return
336+
}
337+
338+
const timeoutId = setTimeout(() => {
339+
onValidationChange(nextStatus)
340+
}, 150)
341+
342+
return () => clearTimeout(timeoutId)
343+
}, [isValidJson, onValidationChange, shouldValidateJson])
320344

321345
// Effects: AI stream handlers setup
322346
useEffect(() => {

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/sub-block.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ const renderLabel = (
190190
<div className='flex items-center gap-[6px] whitespace-nowrap'>
191191
{config.title}
192192
{required && <span className='ml-0.5'>*</span>}
193-
{config.id === 'responseFormat' && (
193+
{config.type === 'code' && config.language === 'json' && (
194194
<Tooltip.Root>
195195
<Tooltip.Trigger asChild>
196196
<AlertTriangle

apps/sim/blocks/blocks/onedrive.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createLogger } from '@/lib/logs/console/logger'
33
import type { BlockConfig } from '@/blocks/types'
44
import { AuthMode } from '@/blocks/types'
55
import type { OneDriveResponse } from '@/tools/onedrive/types'
6+
import { normalizeExcelValuesForToolParams } from '@/tools/onedrive/utils'
67

78
const logger = createLogger('OneDriveBlock')
89

@@ -78,9 +79,10 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
7879
{
7980
id: 'values',
8081
title: 'Values',
81-
type: 'long-input',
82-
placeholder:
83-
'Enter values as JSON array of arrays (e.g., [["A1","B1"],["A2","B2"]]) or an array of objects',
82+
type: 'code',
83+
language: 'json',
84+
generationType: 'json-object',
85+
placeholder: 'Enter a JSON array of rows (e.g., [["A1","B1"],["A2","B2"]])',
8486
condition: {
8587
field: 'operation',
8688
value: 'create_file',
@@ -89,6 +91,13 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
8991
value: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
9092
},
9193
},
94+
wandConfig: {
95+
enabled: true,
96+
prompt:
97+
'Generate a JSON array of arrays that can be written directly into an Excel worksheet.',
98+
placeholder: 'Describe the table you want to generate...',
99+
generationType: 'json-object',
100+
},
92101
required: false,
93102
},
94103
// File upload (basic mode)
@@ -351,17 +360,15 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
351360
params: (params) => {
352361
const { credential, folderId, fileId, mimeType, values, downloadFileName, ...rest } = params
353362

354-
let parsedValues
355-
try {
356-
parsedValues = values ? JSON.parse(values as string) : undefined
357-
} catch (error) {
358-
throw new Error('Invalid JSON format for values')
363+
let normalizedValues: ReturnType<typeof normalizeExcelValuesForToolParams>
364+
if (values !== undefined) {
365+
normalizedValues = normalizeExcelValuesForToolParams(values)
359366
}
360367

361368
return {
362369
credential,
363370
...rest,
364-
values: parsedValues,
371+
values: normalizedValues,
365372
folderId: folderId || undefined,
366373
fileId: fileId || undefined,
367374
pageSize: rest.pageSize ? Number.parseInt(rest.pageSize as string, 10) : undefined,
@@ -380,7 +387,7 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
380387
fileReference: { type: 'json', description: 'File reference from previous block' },
381388
content: { type: 'string', description: 'Text content to upload' },
382389
mimeType: { type: 'string', description: 'MIME type of file to create' },
383-
values: { type: 'string', description: 'Cell values for new Excel as JSON' },
390+
values: { type: 'json', description: 'Cell values for new Excel as JSON' },
384391
fileId: { type: 'string', description: 'File ID to download' },
385392
downloadFileName: { type: 'string', description: 'File name override for download' },
386393
folderId: { type: 'string', description: 'Folder ID' },

apps/sim/blocks/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ export interface SubBlockConfig {
210210
}
211211
})
212212
// Props specific to 'code' sub-block type
213-
language?: 'javascript' | 'json'
213+
language?: 'javascript' | 'json' | 'python'
214214
generationType?: GenerationType
215215
collapsible?: boolean // Whether the code block can be collapsed
216216
defaultCollapsed?: boolean // Whether the code block is collapsed by default

apps/sim/tools/onedrive/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,9 @@ export interface OneDriveToolParams {
9999
pageToken?: string
100100
exportMimeType?: string
101101
// Optional Excel write parameters (used when creating an .xlsx without file content)
102-
values?: (string | number | boolean | null)[][]
102+
values?:
103+
| (string | number | boolean | null)[][]
104+
| Array<Record<string, string | number | boolean | null>>
103105
}
104106

105107
export type OneDriveResponse =

apps/sim/tools/onedrive/utils.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type { OneDriveToolParams } from '@/tools/onedrive/types'
2+
3+
export type ExcelCell = string | number | boolean | null
4+
export type ExcelArrayValues = ExcelCell[][]
5+
export type ExcelObjectValues = Array<Record<string, ExcelCell>>
6+
export type NormalizedExcelValues = ExcelArrayValues | ExcelObjectValues
7+
8+
/**
9+
* Ensures Excel values are always represented as arrays before hitting downstream tooling.
10+
* Accepts JSON strings, array-of-arrays, or array-of-objects and normalizes them.
11+
*/
12+
export function normalizeExcelValues(values: unknown): NormalizedExcelValues | undefined {
13+
if (values === null || values === undefined) {
14+
return undefined
15+
}
16+
17+
if (typeof values === 'string') {
18+
const trimmed = values.trim()
19+
if (!trimmed) {
20+
return undefined
21+
}
22+
23+
try {
24+
const parsed = JSON.parse(trimmed)
25+
if (!Array.isArray(parsed)) {
26+
throw new Error('Excel values must be an array of rows or array of objects')
27+
}
28+
return parsed as NormalizedExcelValues
29+
} catch (_error) {
30+
throw new Error('Invalid JSON format for values')
31+
}
32+
}
33+
34+
if (Array.isArray(values)) {
35+
return values as NormalizedExcelValues
36+
}
37+
38+
throw new Error('Excel values must be an array of rows or array of objects')
39+
}
40+
41+
/**
42+
* Convenience helper for contexts that expect the narrower ToolParams typing.
43+
*/
44+
export function normalizeExcelValuesForToolParams(
45+
values: unknown
46+
): OneDriveToolParams['values'] | undefined {
47+
const normalized = normalizeExcelValues(values)
48+
return normalized as OneDriveToolParams['values'] | undefined
49+
}

0 commit comments

Comments
 (0)