Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions apps/docs/content/docs/integrations/azure_devops.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -400,11 +400,6 @@ Execute a WIQL query to search for work items in Azure DevOps and return full fi
| ↳ `changedBy` | string | Display name of the last user to change the work item |
| ↳ `changedDate` | string | ISO 8601 last-changed timestamp |
| ↳ `webUrl` | string | Browser URL for the work item |
| ↳ `relations` | array | Links to other work items, attachments, and artifacts \(rel such as System.LinkTypes.Hierarchy-Forward or AttachedFile, url, attributes\); null when the response did not include relations \(Create and Update do not expand them\) |
| ↳ `rel` | string | Relation type |
| ↳ `url` | string | URL of the linked resource |
| ↳ `attributes` | json | Link attributes \(name, comment, isLocked\) |
| ↳ `fields` | json | Every field returned for the work item, keyed by reference name \(includes custom and process-specific fields\) |

Comment thread
waleedlatif1 marked this conversation as resolved.
Resolved
</ReferenceDetails>

Expand Down Expand Up @@ -496,11 +491,6 @@ Fetch full details for multiple work items by ID from Azure DevOps. Pass comma-s
| ↳ `changedBy` | string | Display name of the last user to change the work item |
| ↳ `changedDate` | string | ISO 8601 last-changed timestamp |
| ↳ `webUrl` | string | Browser URL for the work item |
| ↳ `relations` | array | Links to other work items, attachments, and artifacts \(rel such as System.LinkTypes.Hierarchy-Forward or AttachedFile, url, attributes\); null when the response did not include relations \(Create and Update do not expand them\) |
| ↳ `rel` | string | Relation type |
| ↳ `url` | string | URL of the linked resource |
| ↳ `attributes` | json | Link attributes \(name, comment, isLocked\) |
| ↳ `fields` | json | Every field returned for the work item, keyed by reference name \(includes custom and process-specific fields\) |

</ReferenceDetails>

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/integrations/onedrive.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Upload a file to OneDrive
| `content` | string | No | The text content to upload \(if no file is provided\) |
| `mimeType` | string | No | The MIME type of the file to create \(e.g., text/plain for .txt, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet for .xlsx\) |
| `folderId` | string | No | Folder ID to upload the file to \(e.g., "01BYE5RZ6QN3ZWBTUFOFD3GSPGOHDJD36M"\) |
| `conflictBehavior` | string | No | What to do when a file with the same name exists: "replace" \(used when unset\), "rename", or "fail" |
| `conflictBehavior` | string | No | What to do when a file with the same name exists: "replace", "rename", or "fail". When unset, Graph applies its default behavior |

#### Output

Expand Down
50 changes: 38 additions & 12 deletions apps/sim/blocks/blocks/azure_devops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ function normalizeDate(input: unknown): string | undefined {
return /^\d{4}-\d{2}-\d{2}$/.test(value) ? `${value}T00:00:00Z` : value
}

/**
* Dedicated Max Results value for an operation, falling back to the shared List Builds `top`
* that these tools read before they had their own field, so saved workflows keep their page size.
*/
function resolveTop(dedicated: unknown, shared: unknown): number | undefined {
const value = dedicated || shared
return value ? Number(value) : undefined
}

export const AzureDevOpsBlock: BlockConfig = {
type: 'azure_devops',
hideFromToolbar: true,
Expand Down Expand Up @@ -318,14 +327,23 @@ export const AzureDevOpsBlock: BlockConfig = {
title: 'Continuation Token',
type: 'short-input',
placeholder: 'From a previous page',
condition: {
field: 'operation',
value: [
'azure_devops_list_builds',
'azure_devops_list_pipelines',
'azure_devops_get_comments',
],
},
condition: { field: 'operation', value: 'azure_devops_list_builds' },
mode: 'advanced',
},
{
id: 'pipelinesContinuationToken',
title: 'Continuation Token',
type: 'short-input',
placeholder: 'From a previous page',
condition: { field: 'operation', value: 'azure_devops_list_pipelines' },
mode: 'advanced',
},
{
id: 'commentsContinuationToken',
title: 'Continuation Token',
type: 'short-input',
placeholder: 'From a previous page',
condition: { field: 'operation', value: 'azure_devops_get_comments' },
mode: 'advanced',
},
{
Expand Down Expand Up @@ -710,8 +728,8 @@ export const AzureDevOpsBlock: BlockConfig = {
case 'azure_devops_list_pipelines':
return {
...base,
top: params.pipelinesTop ? Number(params.pipelinesTop) : undefined,
continuationToken: (params.continuationToken as string) || undefined,
top: resolveTop(params.pipelinesTop, params.top),
continuationToken: (params.pipelinesContinuationToken as string) || undefined,
Comment thread
waleedlatif1 marked this conversation as resolved.
Resolved
Comment thread
waleedlatif1 marked this conversation as resolved.
Resolved
}
case 'azure_devops_get_pipeline':
return { ...base, pipelineId: Number(params.pipelineId) }
Expand Down Expand Up @@ -810,8 +828,8 @@ export const AzureDevOpsBlock: BlockConfig = {
return {
...base,
workItemId: Number(params.workItemId),
top: params.commentsTop ? Number(params.commentsTop) : undefined,
continuationToken: (params.continuationToken as string) || undefined,
top: resolveTop(params.commentsTop, params.top),
continuationToken: (params.commentsContinuationToken as string) || undefined,
}
default:
return base
Expand Down Expand Up @@ -889,6 +907,14 @@ export const AzureDevOpsBlock: BlockConfig = {
minTime: { type: 'string', description: 'Only builds after this ISO 8601 time' },
maxTime: { type: 'string', description: 'Only builds before this ISO 8601 time' },
continuationToken: { type: 'string', description: 'Continuation token for the next page' },
pipelinesContinuationToken: {
type: 'string',
description: 'Continuation token for the next page of pipelines',
},
commentsContinuationToken: {
type: 'string',
description: 'Continuation token for the next page of comments',
},
startLine: { type: 'number', description: 'First log line to return' },
endLine: { type: 'number', description: 'Last log line to return' },
},
Expand Down
16 changes: 12 additions & 4 deletions apps/sim/blocks/blocks/gmail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -971,12 +971,20 @@ export const GmailV2Block: BlockConfig = {
}),
params: (params) => {
const mapped = GmailBlock.tools?.config?.params?.(params) ?? params
const { labelName, targetThreadId, threadFormat, listQuery, threadLabelIds, ...rest } =
mapped
const {
labelName,
targetThreadId,
threadFormat,
listQuery,
threadLabelIds,
messageListVisibility,
labelListVisibility,
...rest
} = mapped
if (LABEL_WRITE_OPERATIONS.includes(rest.operation)) {
rest.name = labelName || undefined
rest.messageListVisibility = rest.messageListVisibility || undefined
rest.labelListVisibility = rest.labelListVisibility || undefined
rest.messageListVisibility = messageListVisibility || undefined
rest.labelListVisibility = labelListVisibility || undefined
}
if (THREAD_TARGET_OPERATIONS.includes(rest.operation)) {
rest.threadId = targetThreadId
Expand Down
10 changes: 4 additions & 6 deletions apps/sim/blocks/blocks/google_drive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1754,15 +1754,13 @@ Return ONLY the comment text - no explanations, no quotes, no extra formatting.`
includeRevisions: includeRevisionsValue,
includeDeleted: includeDeletedValue,
transferOwnership: rest.role === 'owner' ? true : undefined,
orderBy: (effectiveOrderBy as string | undefined)?.trim() || undefined,
driveId: (effectiveDriveId as string | undefined)?.trim() || undefined,
orderBy: effectiveOrderBy?.trim() || undefined,
driveId: effectiveDriveId?.trim() || undefined,
startModifiedTime:
params.operation === 'list_comments'
? (commentsStartModifiedTime as string | undefined)?.trim() || undefined
? commentsStartModifiedTime?.trim() || undefined
: undefined,
expirationTime: isUserOrGroupShare
? (shareExpirationTime as string | undefined)?.trim() || undefined
: undefined,
expirationTime: isUserOrGroupShare ? shareExpirationTime?.trim() || undefined : undefined,
allowFileDiscovery: allowFileDiscoveryValue,
moveToNewOwnersRoot: moveToNewOwnersRootValue,
...rest,
Expand Down
46 changes: 46 additions & 0 deletions apps/sim/blocks/blocks/google_groups.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, expect, it } from 'vitest'
import { GoogleGroupsBlock } from '@/blocks/blocks/google_groups'

/**
* The executor builds tool params as `{ ...inputs, ...transformedParams }`, so an empty dropdown
* value the serializer fills in reaches the tool unless the mapper assigns that key explicitly.
*/
describe('GoogleGroupsBlock update settings', () => {
const mapParams = GoogleGroupsBlock.tools.config?.params as (
params: Record<string, unknown>
) => Record<string, unknown>

it('maps every unchanged setting to undefined so empty values are never sent', () => {
const mapped = mapParams({
operation: 'update_settings',
oauthCredential: 'cred-1',
groupEmail: 'team@example.com',
whoCanJoin: '',
whoCanViewMembership: '',
whoCanViewGroup: '',
whoCanPostMessage: '',
allowExternalMembers: '',
customReplyTo: '',
})
for (const key of [
'whoCanJoin',
'whoCanViewMembership',
'whoCanViewGroup',
'whoCanPostMessage',
'allowExternalMembers',
'customReplyTo',
]) {
expect(mapped).toHaveProperty(key, undefined)
}
})

it('forwards a chosen setting as a string', () => {
const mapped = mapParams({
operation: 'update_settings',
oauthCredential: 'cred-1',
groupEmail: 'team@example.com',
whoCanJoin: 'INVITED_CAN_JOIN',
})
expect(mapped.whoCanJoin).toBe('INVITED_CAN_JOIN')
})
})
9 changes: 6 additions & 3 deletions apps/sim/blocks/blocks/google_groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -829,9 +829,12 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`,
oauthCredential,
groupEmail: rest.groupEmail,
...Object.fromEntries(
UPDATE_SETTINGS_FIELDS.filter(
(key) => rest[key] !== undefined && rest[key] !== null && rest[key] !== ''
).map((key) => [key, String(rest[key])])
UPDATE_SETTINGS_FIELDS.map((key) => [
key,
rest[key] === undefined || rest[key] === null || rest[key] === ''
? undefined
: String(rest[key]),
])
),
}
default:
Expand Down
1 change: 1 addition & 0 deletions apps/sim/blocks/blocks/google_sheets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,7 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`,
...(operation === 'append'
? { valueInputOption: appendValueInputOption || undefined }
: {}),
majorDimension: undefined,
}
},
},
Expand Down
30 changes: 30 additions & 0 deletions apps/sim/blocks/blocks/google_tasks.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, expect, it } from 'vitest'
import { GoogleTasksBlock } from '@/blocks/blocks/google_tasks'

/**
* The executor builds tool params as `{ ...inputs, ...transformedParams }`, so an empty dropdown
* value the serializer fills in reaches the tool unless the mapper assigns that key explicitly.
*/
describe('GoogleTasksBlock list filters', () => {
const mapParams = GoogleTasksBlock.tools.config?.params as (
params: Record<string, unknown>
) => Record<string, unknown>

it('maps unset tri-state filters to undefined', () => {
const mapped = mapParams({
operation: 'list',
oauthCredential: 'cred-1',
showHidden: '',
showDeleted: '',
showAssigned: '',
})
for (const key of ['showHidden', 'showDeleted', 'showAssigned']) {
expect(mapped).toHaveProperty(key, undefined)
}
})

it('converts chosen filters to booleans', () => {
const mapped = mapParams({ operation: 'list', oauthCredential: 'cred-1', showHidden: 'true' })
expect(mapped.showHidden).toBe(true)
})
})
9 changes: 2 additions & 7 deletions apps/sim/blocks/blocks/google_tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { GoogleTasksIcon } from '@/components/icons'
import { getScopesForService } from '@/lib/oauth/utils'
import type { BlockConfig, BlockMeta } from '@/blocks/types'
import { AuthMode, IntegrationType } from '@/blocks/types'
import { SERVICE_ACCOUNT_SUBBLOCKS } from '@/blocks/utils'
import { parseOptionalBooleanInput, SERVICE_ACCOUNT_SUBBLOCKS } from '@/blocks/utils'

/**
* Canonical basic/advanced pair for the task list, shared by the card
Expand Down Expand Up @@ -421,12 +421,7 @@ Return ONLY the timestamp - no explanations, no extra text.`,
}

for (const key of ['showHidden', 'showDeleted', 'showAssigned'] as const) {
const value = processedParams[key]
if (value === '' || value === undefined || value === null) {
delete processedParams[key]
} else if (typeof value === 'string') {
processedParams[key] = value === 'true'
}
processedParams[key] = parseOptionalBooleanInput(processedParams[key])
}

return {
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/blocks/blocks/onedrive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,12 @@ export const OneDriveBlock: BlockConfig = {
title: 'If File Exists',
type: 'dropdown',
options: [
{ label: 'Default', id: '' },
{ label: 'Replace existing file', id: 'replace' },
{ label: 'Keep both (rename new file)', id: 'rename' },
{ label: 'Fail', id: 'fail' },
],
placeholder: 'Replace existing file (default)',
value: () => '',
mode: 'advanced',
condition: { field: 'operation', value: ['create_file', 'upload'] },
},
Expand Down
40 changes: 40 additions & 0 deletions apps/sim/blocks/blocks/outlook.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,43 @@ describe('OutlookBlock calendar operations', () => {
expect(declined?.sendResponse).toBe(false)
})
})

/**
* The executor builds tool params as `{ ...inputs, ...transformedParams }`, so a value stored for
* another operation reaches the tool unless the mapper assigns that key explicitly.
*/
describe('OutlookBlock values stored for another operation', () => {
const mapParams = block.tools.config?.params as (
params: Record<string, unknown>
) => Record<string, unknown>

it.each(['reply_outlook', 'reply_all_outlook', 'forward_outlook'])(
'clears a Send cc/bcc on %s',
(operation) => {
const mapped = mapParams({
operation,
oauthCredential: 'cred-1',
messageId: 'msg-1',
cc: 'boss@example.com',
bcc: 'audit@example.com',
})
expect(mapped).toHaveProperty('cc', null)
expect(mapped).toHaveProperty('bcc', null)
}
)

it.each(['reply_outlook', 'reply_all_outlook', 'forward_outlook'])(
'clears an Update Message importance on %s',
(operation) => {
const mapped = mapParams({ operation, oauthCredential: 'cred-1', importance: 'high' })
expect(mapped).toHaveProperty('importance', undefined)
}
)

it('clears an Update Message importance on send and draft', () => {
for (const operation of ['send_outlook', 'draft_outlook']) {
const mapped = mapParams({ operation, oauthCredential: 'cred-1', importance: 'high' })
expect(mapped).toHaveProperty('importance', null)
}
})
})
20 changes: 9 additions & 11 deletions apps/sim/blocks/blocks/outlook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,10 +305,12 @@ export const OutlookBlock: BlockConfig = {
title: 'Importance',
type: 'dropdown',
options: [
{ label: 'Default', id: '' },
{ label: 'Low', id: 'low' },
{ label: 'Normal', id: 'normal' },
{ label: 'High', id: 'high' },
],
value: () => '',
condition: { field: 'operation', value: ['send_outlook', 'draft_outlook'] },
mode: 'advanced',
required: false,
Expand Down Expand Up @@ -1007,19 +1009,15 @@ Return ONLY the comma-separated email list - no explanations, no extra text.`,
rest.folder = effectiveFolder || 'INBOX'
}

if (['send_outlook', 'draft_outlook'].includes(rest.operation) && mailImportance) {
rest.importance = mailImportance
if (['send_outlook', 'draft_outlook'].includes(rest.operation)) {
rest.importance = mailImportance || null
}

if (['reply_outlook', 'forward_outlook'].includes(rest.operation) && replyCc) {
rest.cc = replyCc
}

if (
['reply_outlook', 'reply_all_outlook', 'forward_outlook'].includes(rest.operation) &&
replyBcc
) {
rest.bcc = replyBcc
// Assign explicitly so a cc/bcc/importance stored for another operation never reaches these tools
if (['reply_outlook', 'reply_all_outlook', 'forward_outlook'].includes(rest.operation)) {
rest.cc = rest.operation === 'reply_all_outlook' ? null : replyCc || null
rest.bcc = replyBcc || null
rest.importance = undefined
}
Comment thread
waleedlatif1 marked this conversation as resolved.
Resolved

if (rest.operation === 'search_outlook' && searchQuery) {
Expand Down
Loading
Loading