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
2 changes: 1 addition & 1 deletion apps/docs/content/docs/integrations/mailchimp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Use [Mailchimp](https://mailchimp.com/) in Sim to manage audiences and members,

**Delete Member from Mailchimp Audience** and **Archive Member from Mailchimp Audience** both call Mailchimp's reversible [archive endpoint](https://mailchimp.com/developer/marketing/api/list-members/archive-list-member); an archived member can be added back.

**Permanently Delete Member from Mailchimp Audience** calls [`POST .../actions/delete-permanent`](https://mailchimp.com/developer/marketing/api/list-member-actions/delete-list-member-permanently), which erases the member's identifying data. It cannot be undone, and the address cannot be re-imported.
**Permanently Delete Member from Mailchimp Audience** calls [`POST .../actions/delete-permanent`](https://mailchimp.com/developer/marketing/api/lists/members/create-member-action-delete-permanent), which erases the member's identifying data. It cannot be undone, and the address cannot be re-imported.
{/* MANUAL-CONTENT-END */}

## Use the block [#usage-instructions]
Expand Down
10 changes: 10 additions & 0 deletions apps/docs/content/docs/integrations/notion.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Read content from a Notion page

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `truncated` | boolean | True when the page has more than 1000 top-level blocks and content stops there |
| `url` | string | Notion page URL |
| `created_time` | string | ISO 8601 creation timestamp |
| `last_edited_time` | string | ISO 8601 last edit timestamp |
Expand Down Expand Up @@ -338,6 +339,7 @@ Append new block children (content) to a Notion page or block
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Page content in markdown format, or comment text for create comment |
| `truncated` | boolean | True when the page has more than 1000 top-level blocks and content stops there |
| `title` | string | Page or database title |
| `url` | string | Notion URL |
| `id` | string | Page, database, block, comment, or user ID |
Expand Down Expand Up @@ -377,6 +379,7 @@ Retrieve a single Notion block by its UUID, including its type-specific content
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Page content in markdown format, or comment text for create comment |
| `truncated` | boolean | True when the page has more than 1000 top-level blocks and content stops there |
| `title` | string | Page or database title |
| `url` | string | Notion URL |
| `id` | string | Page, database, block, comment, or user ID |
Expand Down Expand Up @@ -418,6 +421,7 @@ Retrieve the block children (content) of a Notion page or block
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Page content in markdown format, or comment text for create comment |
| `truncated` | boolean | True when the page has more than 1000 top-level blocks and content stops there |
| `title` | string | Page or database title |
| `url` | string | Notion URL |
| `id` | string | Page, database, block, comment, or user ID |
Expand Down Expand Up @@ -459,6 +463,7 @@ Update the content or archived state of a single Notion block
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Page content in markdown format, or comment text for create comment |
| `truncated` | boolean | True when the page has more than 1000 top-level blocks and content stops there |
| `title` | string | Page or database title |
| `url` | string | Notion URL |
| `id` | string | Page, database, block, comment, or user ID |
Expand Down Expand Up @@ -498,6 +503,7 @@ Delete (move to trash) a single Notion block
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Page content in markdown format, or comment text for create comment |
| `truncated` | boolean | True when the page has more than 1000 top-level blocks and content stops there |
| `title` | string | Page or database title |
| `url` | string | Notion URL |
| `id` | string | Page, database, block, comment, or user ID |
Expand Down Expand Up @@ -539,6 +545,7 @@ Create a comment on a Notion page or within an existing discussion thread
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Page content in markdown format, or comment text for create comment |
| `truncated` | boolean | True when the page has more than 1000 top-level blocks and content stops there |
| `title` | string | Page or database title |
| `url` | string | Notion URL |
| `id` | string | Page, database, block, comment, or user ID |
Expand Down Expand Up @@ -580,6 +587,7 @@ List unresolved comments on a Notion page or block
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Page content in markdown format, or comment text for create comment |
| `truncated` | boolean | True when the page has more than 1000 top-level blocks and content stops there |
| `title` | string | Page or database title |
| `url` | string | Notion URL |
| `id` | string | Page, database, block, comment, or user ID |
Expand Down Expand Up @@ -620,6 +628,7 @@ List all users (members and bots) in the Notion workspace
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Page content in markdown format, or comment text for create comment |
| `truncated` | boolean | True when the page has more than 1000 top-level blocks and content stops there |
| `title` | string | Page or database title |
| `url` | string | Notion URL |
| `id` | string | Page, database, block, comment, or user ID |
Expand Down Expand Up @@ -659,6 +668,7 @@ Retrieve a single Notion user by their UUID
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Page content in markdown format, or comment text for create comment |
| `truncated` | boolean | True when the page has more than 1000 top-level blocks and content stops there |
| `title` | string | Page or database title |
| `url` | string | Notion URL |
| `id` | string | Page, database, block, comment, or user ID |
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/integrations/workday.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ Update fields on an existing worker record in Workday.
| `username` | string | Yes | Integration System User username |
| `password` | string | Yes | Integration System User password |
| `workerId` | string | Yes | Employee ID of the worker to update |
| `fields` | json | Yes | Fields to update as JSON \(e.g., \{"businessTitle": "Senior Engineer", "primaryWorkEmail": "new@company.com"\}\) |
| `fields` | json | Yes | Personal information fields to update as JSON, using Change_Personal_Information_Data element names in schema order \(e.g., \{"Marital_Status_Reference": \{"ID": \{"attributes": \{"wd:type": "Marital_Status_ID"\}, "$value": "Married"\}\}\}\) |

#### Output

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/integrations/zendesk.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ Merge multiple tickets into a target ticket
| --------- | ---- | -------- | ----------- |
| `targetTicketId` | string | Yes | Target ticket ID as a numeric string \(tickets will be merged into this one, e.g., "12345"\) |
| `sourceTicketIds` | string | Yes | Comma-separated source ticket IDs to merge \(e.g., "111, 222, 333"\) |
| `targetComment` | string | No | Comment text to add to target ticket after merge |
| `targetComment` | string | No | Comment text to add to the target ticket after merge, posted as a public reply |

#### Output

Expand Down
24 changes: 12 additions & 12 deletions apps/sim/blocks/blocks/clickup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1802,21 +1802,21 @@ Return ONLY the value (plain string, number, or JSON) - no explanations, no extr
toolCallArg(context, optionalNumber(params.endDate)),
assignee: params.timerAssignee || toolCallArg(context, params.assignee) || undefined,
taskId:
params.timeLocationType === 'task'
? params.timeLocationId || undefined
: toolCallArg(context, params.taskId) || undefined,
(params.timeLocationType === 'task' ? params.timeLocationId : undefined) ||
toolCallArg(context, params.taskId) ||
undefined,
listId:
params.timeLocationType === 'list'
? params.timeLocationId || undefined
: toolCallArg(context, params.listId) || undefined,
(params.timeLocationType === 'list' ? params.timeLocationId : undefined) ||
toolCallArg(context, params.listId) ||
undefined,
folderId:
params.timeLocationType === 'folder'
? params.timeLocationId || undefined
: toolCallArg(context, params.folderId) || undefined,
(params.timeLocationType === 'folder' ? params.timeLocationId : undefined) ||
toolCallArg(context, params.folderId) ||
undefined,
spaceId:
params.timeLocationType === 'space'
? params.timeLocationId || undefined
: toolCallArg(context, params.spaceId) || undefined,
(params.timeLocationType === 'space' ? params.timeLocationId : undefined) ||
toolCallArg(context, params.spaceId) ||
undefined,
includeTaskTags: params.includeTaskTags ? true : undefined,
includeLocationNames: params.includeLocationNames ? true : undefined,
}
Expand Down
30 changes: 30 additions & 0 deletions apps/sim/blocks/blocks/docusign.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, expect, it } from 'vitest'
import { DocuSignBlock } from '@/blocks/blocks/docusign'

function runCanvas(params: Record<string, unknown>) {
const mapper = DocuSignBlock.tools.config?.params
if (!mapper) throw new Error('DocuSign block has no params mapper')
return { ...params, ...mapper(params) }
}

describe('DocuSign List Envelopes status filter', () => {
it('does not filter by the stored Send Envelope status when the list filter is All', () => {
const merged = runCanvas({
operation: 'list_envelopes',
envelopeStatus: 'sent',
listEnvelopeStatus: '',
})

expect(merged.envelopeStatus).toBeUndefined()
})

it('sends the chosen list filter', () => {
const merged = runCanvas({
operation: 'list_envelopes',
envelopeStatus: 'sent',
listEnvelopeStatus: 'completed',
})

expect(merged.envelopeStatus).toBe('completed')
})
})
14 changes: 10 additions & 4 deletions apps/sim/blocks/blocks/docusign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { DocuSignIcon } from '@/components/icons'
import { getScopesForService } from '@/lib/oauth/utils'
import type { BlockConfig, BlockMeta } from '@/blocks/types'
import { AuthMode, IntegrationType } from '@/blocks/types'
import { normalizeFileInput } from '@/blocks/utils'
import { normalizeFileInput, toolCallArg } from '@/blocks/utils'

const DOCUMENT_FIELD = ['uploadDocument', 'documentRef'] as const

Expand Down Expand Up @@ -314,7 +314,7 @@ export const DocuSignBlock: BlockConfig = {
],
config: {
tool: (params) => `docusign_${params.operation}`,
params: (params) => {
params: (params, context) => {
const { oauthCredential, operation, documentFile, listEnvelopeStatus, ...rest } = params

const cleanParams: Record<string, unknown> = {
Expand All @@ -326,8 +326,14 @@ export const DocuSignBlock: BlockConfig = {
cleanParams.file = file
}

if (listEnvelopeStatus && operation === 'list_envelopes') {
cleanParams.envelopeStatus = listEnvelopeStatus
/**
* The Send/Template status dropdown stores `sent` on every new block, and the executor
* merges raw inputs under this result, so the list filter is always assigned explicitly:
* otherwise that stored `sent` reached List Envelopes and "All" listed only sent envelopes.
*/
if ((operation ?? context?.operation) === 'list_envelopes') {
cleanParams.envelopeStatus =
listEnvelopeStatus || toolCallArg(context, rest.envelopeStatus) || undefined
Comment thread
waleedlatif1 marked this conversation as resolved.
Resolved
}

if (operation === 'create_from_template') {
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/blocks/blocks/intercom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,7 @@ Return ONLY the subject line - no explanations.`,
options: [
{ label: 'User', id: 'user' },
{ label: 'Lead', id: 'lead' },
{ label: 'Contact', id: 'contact' },
{ label: 'Contact (legacy)', id: 'contact' },
],
required: true,
condition: {
Expand Down
5 changes: 5 additions & 0 deletions apps/sim/blocks/blocks/notion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1028,6 +1028,11 @@ export const NotionV2Block: BlockConfig = {
description: 'Page content in markdown format, or comment text for create comment',
condition: { field: 'operation', value: ['notion_read', 'notion_create_comment'] },
},
truncated: {
type: 'boolean',
description: 'True when the page has more than 1000 top-level blocks and content stops there',
condition: { field: 'operation', value: 'notion_read' },
},
title: {
type: 'string',
description: 'Page or database title',
Expand Down
35 changes: 35 additions & 0 deletions apps/sim/blocks/blocks/sap_concur.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { describe, expect, it } from 'vitest'
import { SapConcurBlock } from '@/blocks/blocks/sap_concur'

const operation = 'sap_concur_list_travel_requests'

function mapParams(params: Record<string, unknown>, context?: { operation: string }) {
const mapper = SapConcurBlock.tools.config?.params
if (!mapper) throw new Error('SAP Concur block has no params mapper')
return mapper({ operation, ...params }, context)
}

describe('SAP Concur list travel requests mapping', () => {
it('keeps the model user and sort arguments on a tool call', () => {
const mapped = mapParams(
{ userId: 'user-1', sortField: 'startDate', sortOrder: 'DESC' },
{ operation }
)

expect(mapped.userId).toBe('user-1')
expect(mapped.sortField).toBe('startDate')
expect(mapped.sortOrder).toBe('DESC')
})

it('prefers the configured subBlock over the model argument', () => {
const mapped = mapParams({ travelRequestUserId: 'configured', userId: 'model' }, { operation })

expect(mapped.userId).toBe('configured')
})

it('does not forward a stale same-named value on the canvas', () => {
const mapped = mapParams({ userId: 'stale-from-another-operation' })

expect(mapped.userId).toBeUndefined()
})
})
8 changes: 4 additions & 4 deletions apps/sim/lib/internal/docusign/execute-tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@ const schemas = {
toDate: z.string().optional(),
envelopeStatus: z.string().optional(),
searchText: z.string().optional(),
count: z.string().optional(),
startPosition: z.string().optional(),
count: z.union([z.string(), z.number()]).transform(String).optional(),
startPosition: z.union([z.string(), z.number()]).transform(String).optional(),
}),
docusign_list_recipients: z.object({ ...auth, envelopeId: z.string().min(1) }),
docusign_list_templates: z.object({
...auth,
searchText: z.string().optional(),
count: z.string().optional(),
startPosition: z.string().optional(),
count: z.union([z.string(), z.number()]).transform(String).optional(),
startPosition: z.union([z.string(), z.number()]).transform(String).optional(),
}),
docusign_send_envelope: z.object({
...auth,
Expand Down
26 changes: 26 additions & 0 deletions apps/sim/lib/internal/workday/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
createWorkdaySoapClient,
extractRefId,
} from '@/lib/internal/workday/client'
import { executeWorkdayGetOrganizations } from '@/lib/internal/workday/operations'

const SUCCESS_RESPONSE = `<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wd="urn:com.workday/bsvc">
Expand Down Expand Up @@ -39,6 +40,31 @@ describe('Workday SOAP client', () => {
)
})

it('sends Get_Organizations criteria before the response filter, in schema order', async () => {
fetchMock.mockResolvedValue(
new Response(
'<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wd="urn:com.workday/bsvc"><env:Body><wd:Get_Organizations_Response/></env:Body></env:Envelope>',
{ status: 200 }
)
)

await executeWorkdayGetOrganizations({
tenantUrl: 'https://wd2-impl-services1.workday.com',
tenant: 'example',
username: 'user',
password: 'password',
type: 'Supervisory',
})

const body = String(fetchMock.mock.calls[0]?.[1]?.body)
const criteria = body.indexOf('<wd:Request_Criteria>')
const filter = body.indexOf('<wd:Response_Filter>')
const group = body.indexOf('<wd:Response_Group>')
expect(criteria).toBeGreaterThan(-1)
expect(criteria).toBeLessThan(filter)
expect(filter).toBeLessThan(group)
})

it('passes cancellation to fetch, escapes XML, and parses SOAP responses', async () => {
fetchMock.mockResolvedValue(new Response(SUCCESS_RESPONSE, { status: 200 }))
const controller = new AbortController()
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/lib/internal/workday/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,14 +281,14 @@ export async function executeWorkdayGetOrganizations(
const offset = input.offset ?? 0
const page = offset > 0 ? Math.floor(offset / limit) + 1 : 1
const [result] = await client.Get_OrganizationsAsync({
Response_Filter: { Page: page, Count: limit },
Request_Criteria: input.type
? {
Organization_Type_Reference: {
ID: { attributes: { 'wd:type': 'Organization_Type_ID' }, $value: input.type },
},
}
: undefined,
Response_Filter: { Page: page, Count: limit },
Response_Group: { Include_Hierarchy_Data: true },
})
signal?.throwIfAborted()
Expand Down
25 changes: 25 additions & 0 deletions apps/sim/lib/selectors/server/providers/pipedrive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,31 @@ describe('Pipedrive server selector adapter', () => {

afterAll(() => vi.unstubAllGlobals())

it('lists pipelines from API v2 and follows next_cursor', async () => {
mockFetch
.mockResolvedValueOnce(
Response.json({
success: true,
data: [{ id: 1, name: 'Sales' }],
additional_data: { next_cursor: 'cursor-2' },
})
)
.mockResolvedValueOnce(
Response.json({
success: true,
data: [{ id: 2, name: 'Partners' }],
additional_data: { next_cursor: null },
})
)

const result = await pipedriveSelectorAttachments['pipedrive.pipelines'].execute(args())

expect(JSON.stringify(result)).toContain('Partners')
const urls = mockFetch.mock.calls.map(([url]) => String(url))
expect(urls[0]).toContain('https://api.pipedrive.com/api/v2/pipelines')
expect(urls[1]).toContain('cursor=cursor-2')
})

it('rejects a semantic failure instead of returning an empty pipeline list', async () => {
mockFetch.mockResolvedValueOnce(
new Response(
Expand Down
Loading
Loading