Skip to content

Commit 3eb9bee

Browse files
committed
fix(integrations): close stale and empty value leaks from the Google and Microsoft sweeps
Final staging-to-main review of #9947, #9954 and #9949. The executor merges raw subBlock values under the block mapper's output, so a value stored for another operation (or an empty default the serializer fills in) reached tools whenever the mapper skipped that key. - Outlook: reply, reply-all and forward always set cc/bcc, so a Send cc never reaches them; send and draft always set importance; the new Importance dropdown defaults to unset - Google Groups Update Settings assigns every setting, so unchanged settings are never sent as '' - Google Tasks List assigns its tri-state filters and the tool skips empty values - OneDrive "If File Exists" defaults to unset so opening an old upload block sends nothing new - Google Sheets clears a Batch Get major dimension for every other operation - Azure DevOps: List Pipelines and Get Comments keep main's Max Results fallback, each paged operation has its own continuation token, and multi-item reads omit the raw field bag - SharePoint upload drops "." folder segments as before and still rejects ".." - Entra transitive and includeManager use one strict flag check; Dataverse treats a cleared page size as unset - Behaviour-neutral cleanups: shared header lookup (Gmail), settings mapper (Groups), identity and metadata outputs (Word), isSet (Dataverse), graphPathSegment (SharePoint), typed identity reads (Azure DevOps), Teams fallback payload nulls
1 parent 4388745 commit 3eb9bee

51 files changed

Lines changed: 414 additions & 402 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/docs/content/docs/integrations/azure_devops.mdx‎

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -388,23 +388,6 @@ Execute a WIQL query to search for work items in Azure DevOps and return full fi
388388
| ↳ `assignedTo` | string | Display name of assigned user, or null if unassigned |
389389
| ↳ `areaPath` | string | Area path of the work item |
390390
| ↳ `url` | string | API URL for the work item |
391-
| ↳ `rev` | number | Revision number of the work item |
392-
| ↳ `description` | string | Description \(System.Description, HTML\) |
393-
| ↳ `iterationPath` | string | Iteration path \(System.IterationPath\) |
394-
| ↳ `reason` | string | Reason for the current state \(System.Reason\) |
395-
| ↳ `tags` | string | Semicolon-separated tags \(System.Tags\) |
396-
| ↳ `priority` | number | Priority \(Microsoft.VSTS.Common.Priority\) |
397-
| ↳ `assignedToUniqueName` | string | Unique name \(usually the email\) of the assigned user |
398-
| ↳ `createdBy` | string | Display name of the creator |
399-
| ↳ `createdDate` | string | ISO 8601 creation timestamp |
400-
| ↳ `changedBy` | string | Display name of the last user to change the work item |
401-
| ↳ `changedDate` | string | ISO 8601 last-changed timestamp |
402-
| ↳ `webUrl` | string | Browser URL for the work item |
403-
| ↳ `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\) |
404-
| ↳ `rel` | string | Relation type |
405-
| ↳ `url` | string | URL of the linked resource |
406-
| ↳ `attributes` | json | Link attributes \(name, comment, isLocked\) |
407-
| ↳ `fields` | json | Every field returned for the work item, keyed by reference name \(includes custom and process-specific fields\) |
408391

409392
</ReferenceDetails>
410393

@@ -484,23 +467,6 @@ Fetch full details for multiple work items by ID from Azure DevOps. Pass comma-s
484467
| ↳ `assignedTo` | string | Display name of assigned user, or null if unassigned |
485468
| ↳ `areaPath` | string | Area path of the work item |
486469
| ↳ `url` | string | API URL for the work item |
487-
| ↳ `rev` | number | Revision number of the work item |
488-
| ↳ `description` | string | Description \(System.Description, HTML\) |
489-
| ↳ `iterationPath` | string | Iteration path \(System.IterationPath\) |
490-
| ↳ `reason` | string | Reason for the current state \(System.Reason\) |
491-
| ↳ `tags` | string | Semicolon-separated tags \(System.Tags\) |
492-
| ↳ `priority` | number | Priority \(Microsoft.VSTS.Common.Priority\) |
493-
| ↳ `assignedToUniqueName` | string | Unique name \(usually the email\) of the assigned user |
494-
| ↳ `createdBy` | string | Display name of the creator |
495-
| ↳ `createdDate` | string | ISO 8601 creation timestamp |
496-
| ↳ `changedBy` | string | Display name of the last user to change the work item |
497-
| ↳ `changedDate` | string | ISO 8601 last-changed timestamp |
498-
| ↳ `webUrl` | string | Browser URL for the work item |
499-
| ↳ `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\) |
500-
| ↳ `rel` | string | Relation type |
501-
| ↳ `url` | string | URL of the linked resource |
502-
| ↳ `attributes` | json | Link attributes \(name, comment, isLocked\) |
503-
| ↳ `fields` | json | Every field returned for the work item, keyed by reference name \(includes custom and process-specific fields\) |
504470

505471
</ReferenceDetails>
506472

‎apps/docs/content/docs/integrations/onedrive.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ Upload a file to OneDrive
5252
| `content` | string | No | The text content to upload \(if no file is provided\) |
5353
| `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\) |
5454
| `folderId` | string | No | Folder ID to upload the file to \(e.g., "01BYE5RZ6QN3ZWBTUFOFD3GSPGOHDJD36M"\) |
55-
| `conflictBehavior` | string | No | What to do when a file with the same name exists: "replace" \(used when unset\), "rename", or "fail" |
55+
| `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 |
5656

5757
#### Output
5858

‎apps/sim/blocks/blocks/azure_devops.ts‎

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ function normalizeDate(input: unknown): string | undefined {
1313
return /^\d{4}-\d{2}-\d{2}$/.test(value) ? `${value}T00:00:00Z` : value
1414
}
1515

16+
/**
17+
* Dedicated Max Results value for an operation, falling back to the shared List Builds `top`
18+
* that these tools read before they had their own field, so saved workflows keep their page size.
19+
*/
20+
function resolveTop(dedicated: unknown, shared: unknown): number | undefined {
21+
const value = dedicated || shared
22+
return value ? Number(value) : undefined
23+
}
24+
1625
export const AzureDevOpsBlock: BlockConfig = {
1726
type: 'azure_devops',
1827
hideFromToolbar: true,
@@ -318,14 +327,23 @@ export const AzureDevOpsBlock: BlockConfig = {
318327
title: 'Continuation Token',
319328
type: 'short-input',
320329
placeholder: 'From a previous page',
321-
condition: {
322-
field: 'operation',
323-
value: [
324-
'azure_devops_list_builds',
325-
'azure_devops_list_pipelines',
326-
'azure_devops_get_comments',
327-
],
328-
},
330+
condition: { field: 'operation', value: 'azure_devops_list_builds' },
331+
mode: 'advanced',
332+
},
333+
{
334+
id: 'pipelinesContinuationToken',
335+
title: 'Continuation Token',
336+
type: 'short-input',
337+
placeholder: 'From a previous page',
338+
condition: { field: 'operation', value: 'azure_devops_list_pipelines' },
339+
mode: 'advanced',
340+
},
341+
{
342+
id: 'commentsContinuationToken',
343+
title: 'Continuation Token',
344+
type: 'short-input',
345+
placeholder: 'From a previous page',
346+
condition: { field: 'operation', value: 'azure_devops_get_comments' },
329347
mode: 'advanced',
330348
},
331349
{
@@ -710,8 +728,8 @@ export const AzureDevOpsBlock: BlockConfig = {
710728
case 'azure_devops_list_pipelines':
711729
return {
712730
...base,
713-
top: params.pipelinesTop ? Number(params.pipelinesTop) : undefined,
714-
continuationToken: (params.continuationToken as string) || undefined,
731+
top: resolveTop(params.pipelinesTop, params.top),
732+
continuationToken: (params.pipelinesContinuationToken as string) || undefined,
715733
}
716734
case 'azure_devops_get_pipeline':
717735
return { ...base, pipelineId: Number(params.pipelineId) }
@@ -810,8 +828,8 @@ export const AzureDevOpsBlock: BlockConfig = {
810828
return {
811829
...base,
812830
workItemId: Number(params.workItemId),
813-
top: params.commentsTop ? Number(params.commentsTop) : undefined,
814-
continuationToken: (params.continuationToken as string) || undefined,
831+
top: resolveTop(params.commentsTop, params.top),
832+
continuationToken: (params.commentsContinuationToken as string) || undefined,
815833
}
816834
default:
817835
return base
@@ -889,6 +907,14 @@ export const AzureDevOpsBlock: BlockConfig = {
889907
minTime: { type: 'string', description: 'Only builds after this ISO 8601 time' },
890908
maxTime: { type: 'string', description: 'Only builds before this ISO 8601 time' },
891909
continuationToken: { type: 'string', description: 'Continuation token for the next page' },
910+
pipelinesContinuationToken: {
911+
type: 'string',
912+
description: 'Continuation token for the next page of pipelines',
913+
},
914+
commentsContinuationToken: {
915+
type: 'string',
916+
description: 'Continuation token for the next page of comments',
917+
},
892918
startLine: { type: 'number', description: 'First log line to return' },
893919
endLine: { type: 'number', description: 'Last log line to return' },
894920
},

‎apps/sim/blocks/blocks/gmail.ts‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -971,12 +971,20 @@ export const GmailV2Block: BlockConfig = {
971971
}),
972972
params: (params) => {
973973
const mapped = GmailBlock.tools?.config?.params?.(params) ?? params
974-
const { labelName, targetThreadId, threadFormat, listQuery, threadLabelIds, ...rest } =
975-
mapped
974+
const {
975+
labelName,
976+
targetThreadId,
977+
threadFormat,
978+
listQuery,
979+
threadLabelIds,
980+
messageListVisibility,
981+
labelListVisibility,
982+
...rest
983+
} = mapped
976984
if (LABEL_WRITE_OPERATIONS.includes(rest.operation)) {
977985
rest.name = labelName || undefined
978-
rest.messageListVisibility = rest.messageListVisibility || undefined
979-
rest.labelListVisibility = rest.labelListVisibility || undefined
986+
rest.messageListVisibility = messageListVisibility || undefined
987+
rest.labelListVisibility = labelListVisibility || undefined
980988
}
981989
if (THREAD_TARGET_OPERATIONS.includes(rest.operation)) {
982990
rest.threadId = targetThreadId

‎apps/sim/blocks/blocks/google_drive.ts‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1754,15 +1754,13 @@ Return ONLY the comment text - no explanations, no quotes, no extra formatting.`
17541754
includeRevisions: includeRevisionsValue,
17551755
includeDeleted: includeDeletedValue,
17561756
transferOwnership: rest.role === 'owner' ? true : undefined,
1757-
orderBy: (effectiveOrderBy as string | undefined)?.trim() || undefined,
1758-
driveId: (effectiveDriveId as string | undefined)?.trim() || undefined,
1757+
orderBy: effectiveOrderBy?.trim() || undefined,
1758+
driveId: effectiveDriveId?.trim() || undefined,
17591759
startModifiedTime:
17601760
params.operation === 'list_comments'
1761-
? (commentsStartModifiedTime as string | undefined)?.trim() || undefined
1761+
? commentsStartModifiedTime?.trim() || undefined
17621762
: undefined,
1763-
expirationTime: isUserOrGroupShare
1764-
? (shareExpirationTime as string | undefined)?.trim() || undefined
1765-
: undefined,
1763+
expirationTime: isUserOrGroupShare ? shareExpirationTime?.trim() || undefined : undefined,
17661764
allowFileDiscovery: allowFileDiscoveryValue,
17671765
moveToNewOwnersRoot: moveToNewOwnersRootValue,
17681766
...rest,
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { GoogleGroupsBlock } from '@/blocks/blocks/google_groups'
3+
4+
/**
5+
* The executor builds tool params as `{ ...inputs, ...transformedParams }`, so an empty dropdown
6+
* value the serializer fills in reaches the tool unless the mapper assigns that key explicitly.
7+
*/
8+
describe('GoogleGroupsBlock update settings', () => {
9+
const mapParams = GoogleGroupsBlock.tools.config?.params as (
10+
params: Record<string, unknown>
11+
) => Record<string, unknown>
12+
13+
it('maps every unchanged setting to undefined so empty values are never sent', () => {
14+
const mapped = mapParams({
15+
operation: 'update_settings',
16+
oauthCredential: 'cred-1',
17+
groupEmail: 'team@example.com',
18+
whoCanJoin: '',
19+
whoCanViewMembership: '',
20+
whoCanViewGroup: '',
21+
whoCanPostMessage: '',
22+
allowExternalMembers: '',
23+
customReplyTo: '',
24+
})
25+
for (const key of [
26+
'whoCanJoin',
27+
'whoCanViewMembership',
28+
'whoCanViewGroup',
29+
'whoCanPostMessage',
30+
'allowExternalMembers',
31+
'customReplyTo',
32+
]) {
33+
expect(mapped).toHaveProperty(key, undefined)
34+
}
35+
})
36+
37+
it('forwards a chosen setting as a string', () => {
38+
const mapped = mapParams({
39+
operation: 'update_settings',
40+
oauthCredential: 'cred-1',
41+
groupEmail: 'team@example.com',
42+
whoCanJoin: 'INVITED_CAN_JOIN',
43+
})
44+
expect(mapped.whoCanJoin).toBe('INVITED_CAN_JOIN')
45+
})
46+
})

‎apps/sim/blocks/blocks/google_groups.ts‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -829,9 +829,12 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`,
829829
oauthCredential,
830830
groupEmail: rest.groupEmail,
831831
...Object.fromEntries(
832-
UPDATE_SETTINGS_FIELDS.filter(
833-
(key) => rest[key] !== undefined && rest[key] !== null && rest[key] !== ''
834-
).map((key) => [key, String(rest[key])])
832+
UPDATE_SETTINGS_FIELDS.map((key) => [
833+
key,
834+
rest[key] === undefined || rest[key] === null || rest[key] === ''
835+
? undefined
836+
: String(rest[key]),
837+
])
835838
),
836839
}
837840
default:

‎apps/sim/blocks/blocks/google_sheets.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,7 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`,
10811081
...(operation === 'append'
10821082
? { valueInputOption: appendValueInputOption || undefined }
10831083
: {}),
1084+
majorDimension: undefined,
10841085
}
10851086
},
10861087
},
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { GoogleTasksBlock } from '@/blocks/blocks/google_tasks'
3+
4+
/**
5+
* The executor builds tool params as `{ ...inputs, ...transformedParams }`, so an empty dropdown
6+
* value the serializer fills in reaches the tool unless the mapper assigns that key explicitly.
7+
*/
8+
describe('GoogleTasksBlock list filters', () => {
9+
const mapParams = GoogleTasksBlock.tools.config?.params as (
10+
params: Record<string, unknown>
11+
) => Record<string, unknown>
12+
13+
it('maps unset tri-state filters to undefined', () => {
14+
const mapped = mapParams({
15+
operation: 'list',
16+
oauthCredential: 'cred-1',
17+
showHidden: '',
18+
showDeleted: '',
19+
showAssigned: '',
20+
})
21+
for (const key of ['showHidden', 'showDeleted', 'showAssigned']) {
22+
expect(mapped).toHaveProperty(key, undefined)
23+
}
24+
})
25+
26+
it('converts chosen filters to booleans', () => {
27+
const mapped = mapParams({ operation: 'list', oauthCredential: 'cred-1', showHidden: 'true' })
28+
expect(mapped.showHidden).toBe(true)
29+
})
30+
})

‎apps/sim/blocks/blocks/google_tasks.ts‎

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { GoogleTasksIcon } from '@/components/icons'
22
import { getScopesForService } from '@/lib/oauth/utils'
33
import type { BlockConfig, BlockMeta } from '@/blocks/types'
44
import { AuthMode, IntegrationType } from '@/blocks/types'
5-
import { SERVICE_ACCOUNT_SUBBLOCKS } from '@/blocks/utils'
5+
import { parseOptionalBooleanInput, SERVICE_ACCOUNT_SUBBLOCKS } from '@/blocks/utils'
66

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

423423
for (const key of ['showHidden', 'showDeleted', 'showAssigned'] as const) {
424-
const value = processedParams[key]
425-
if (value === '' || value === undefined || value === null) {
426-
delete processedParams[key]
427-
} else if (typeof value === 'string') {
428-
processedParams[key] = value === 'true'
429-
}
424+
processedParams[key] = parseOptionalBooleanInput(processedParams[key])
430425
}
431426

432427
return {

0 commit comments

Comments
 (0)