Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
364bb19
feat(credentials): multiple credentials per provider (#3211)
icecrasher321 Feb 23, 2026
e55d41f
fix(credentials): credential dependent endpoints (#3309)
icecrasher321 Feb 23, 2026
2ae8145
improvement(migration): move credential selector automigration logic …
icecrasher321 Feb 23, 2026
132fef0
fix(redis): tighten stale TCP connection detection and add fast lease…
waleedlatif1 Feb 23, 2026
eafbb9f
fix(tag-dropdown): exclude downstream blocks in loops and parallel si…
waleedlatif1 Feb 23, 2026
9166649
fix(execution): scope X-Sim-Via header to internal routes and enforce…
waleedlatif1 Feb 23, 2026
b8dfb4d
fix(copy): preserve block names when pasting into workflows without c…
waleedlatif1 Feb 23, 2026
fe34d23
feat(gong): add Gong integration with 18 API tools (#3316)
waleedlatif1 Feb 24, 2026
d4a014f
feat(public-api): add env var and permission group controls to disabl…
waleedlatif1 Feb 24, 2026
9bd357f
improvement(audit): enrich metadata across 23 audit log call sites (#…
waleedlatif1 Feb 24, 2026
d824ce5
feat(confluence): add webhook triggers for Confluence events (#3318)
waleedlatif1 Feb 24, 2026
9e817bc
fix(auth): make DISABLE_AUTH work in web app (#3297)
jayy-77 Feb 24, 2026
9a31c7d
improvement(processing): reduce redundant DB queries in execution pre…
waleedlatif1 Feb 24, 2026
60f9eb2
feat(attio): add Attio CRM integration with 40 tools and 18 webhook t…
waleedlatif1 Feb 24, 2026
8f9b859
improvement(credentials): ui (#3322)
emir-karabeg Feb 24, 2026
0574427
fix(providers): propagate abort signal to all LLM SDK calls (#3325)
waleedlatif1 Feb 24, 2026
d06459f
fix(attio): automatic webhook lifecycle management and tool fixes (#3…
waleedlatif1 Feb 25, 2026
ecdb133
improvement(creds): bulk paste functionality, save notification, erro…
waleedlatif1 Feb 25, 2026
58d0fda
fix(serializer): default canonical modes construction (#3330)
icecrasher321 Feb 25, 2026
ff01825
docs(credentials): replace environment variables page with credential…
waleedlatif1 Feb 25, 2026
43c0f5b
feat(api): retry configuration for api block (#3329)
waleedlatif1 Feb 25, 2026
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
Prev Previous commit
Next Next commit
fix(copy): preserve block names when pasting into workflows without c…
…onflicts (#3315)
  • Loading branch information
waleedlatif1 authored Feb 23, 2026
commit b8dfb4dd20239aff8bd0ac962196cc1dffb4a82d
259 changes: 258 additions & 1 deletion apps/sim/stores/workflows/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,9 +527,266 @@ describe('regenerateBlockIds', () => {
const newBlocks = Object.values(result.blocks)
expect(newBlocks).toHaveLength(2)

// All pasted blocks should be unlocked so users can edit them
for (const block of newBlocks) {
expect(block.locked).toBe(false)
}
})

it('should preserve original name when no conflicting block exists', () => {
const blockId = 'block-1'

const blocksToCopy = {
[blockId]: createAgentBlock({
id: blockId,
name: 'Agent 1',
position: { x: 100, y: 100 },
}),
}

const result = regenerateBlockIds(
blocksToCopy,
[],
{},
{},
{},
positionOffset,
{},
getUniqueBlockName
)

const newBlocks = Object.values(result.blocks)
expect(newBlocks).toHaveLength(1)
expect(newBlocks[0].name).toBe('Agent 1')
})

it('should preserve original name with number suffix when no conflict', () => {
const blocksToCopy = {
'block-1': createAgentBlock({
id: 'block-1',
name: 'Agent 3',
position: { x: 100, y: 100 },
}),
}

const result = regenerateBlockIds(
blocksToCopy,
[],
{},
{},
{},
positionOffset,
{},
getUniqueBlockName
)

expect(Object.values(result.blocks)[0].name).toBe('Agent 3')
})

it('should increment name when an exact match exists in destination', () => {
const existingBlocks = {
existing: createAgentBlock({ id: 'existing', name: 'Agent 1' }),
}

const blocksToCopy = {
'block-1': createAgentBlock({
id: 'block-1',
name: 'Agent 1',
position: { x: 100, y: 100 },
}),
}

const result = regenerateBlockIds(
blocksToCopy,
[],
{},
{},
{},
positionOffset,
existingBlocks,
getUniqueBlockName
)

expect(Object.values(result.blocks)[0].name).toBe('Agent 2')
})

it('should preserve name when only a different-numbered sibling exists', () => {
const existingBlocks = {
existing: createAgentBlock({ id: 'existing', name: 'Agent 2' }),
}

const blocksToCopy = {
'block-1': createAgentBlock({
id: 'block-1',
name: 'Agent 5',
position: { x: 100, y: 100 },
}),
}

const result = regenerateBlockIds(
blocksToCopy,
[],
{},
{},
{},
positionOffset,
existingBlocks,
getUniqueBlockName
)

expect(Object.values(result.blocks)[0].name).toBe('Agent 5')
})

it('should preserve names for multiple blocks when no conflicts', () => {
const blocksToCopy = {
'block-1': createAgentBlock({
id: 'block-1',
name: 'Agent 1',
position: { x: 100, y: 100 },
}),
'block-2': createFunctionBlock({
id: 'block-2',
name: 'Function 3',
position: { x: 200, y: 100 },
}),
}

const result = regenerateBlockIds(
blocksToCopy,
[],
{},
{},
{},
positionOffset,
{},
getUniqueBlockName
)

const newBlocks = Object.values(result.blocks)
const agentBlock = newBlocks.find((b) => b.type === 'agent')
const functionBlock = newBlocks.find((b) => b.type === 'function')

expect(agentBlock!.name).toBe('Agent 1')
expect(functionBlock!.name).toBe('Function 3')
})

it('should handle mixed conflicts: preserve non-conflicting, increment conflicting', () => {
const existingBlocks = {
existing: createAgentBlock({ id: 'existing', name: 'Agent 1' }),
}

const blocksToCopy = {
'block-1': createAgentBlock({
id: 'block-1',
name: 'Agent 1',
position: { x: 100, y: 100 },
}),
'block-2': createFunctionBlock({
id: 'block-2',
name: 'Function 1',
position: { x: 200, y: 100 },
}),
}

const result = regenerateBlockIds(
blocksToCopy,
[],
{},
{},
{},
positionOffset,
existingBlocks,
getUniqueBlockName
)

const newBlocks = Object.values(result.blocks)
const agentBlock = newBlocks.find((b) => b.type === 'agent')
const functionBlock = newBlocks.find((b) => b.type === 'function')

expect(agentBlock!.name).toBe('Agent 2')
expect(functionBlock!.name).toBe('Function 1')
})

it('should detect conflicts case-insensitively', () => {
const existingBlocks = {
existing: createBlock({ id: 'existing', name: 'api 1' }),
}

const blocksToCopy = {
'block-1': createBlock({
id: 'block-1',
name: 'API 1',
position: { x: 100, y: 100 },
}),
}

const result = regenerateBlockIds(
blocksToCopy,
[],
{},
{},
{},
positionOffset,
existingBlocks,
getUniqueBlockName
)

expect(Object.values(result.blocks)[0].name).toBe('API 2')
})

it('should preserve name without number suffix when no conflict', () => {
const blocksToCopy = {
'block-1': createBlock({
id: 'block-1',
name: 'Custom Block',
position: { x: 100, y: 100 },
}),
}

const result = regenerateBlockIds(
blocksToCopy,
[],
{},
{},
{},
positionOffset,
{},
getUniqueBlockName
)

expect(Object.values(result.blocks)[0].name).toBe('Custom Block')
})

it('should avoid collisions between pasted blocks themselves', () => {
const blocksToCopy = {
'block-1': createAgentBlock({
id: 'block-1',
name: 'Agent 1',
position: { x: 100, y: 100 },
}),
'block-2': createAgentBlock({
id: 'block-2',
name: 'Agent 1',
position: { x: 200, y: 100 },
}),
}

const result = regenerateBlockIds(
blocksToCopy,
[],
{},
{},
{},
positionOffset,
{},
getUniqueBlockName
)

const newBlocks = Object.values(result.blocks)
const names = newBlocks.map((b) => b.name)

expect(names).toHaveLength(2)
expect(new Set(names).size).toBe(2)
expect(names).toContain('Agent 1')
expect(names).toContain('Agent 2')
})
})
5 changes: 4 additions & 1 deletion apps/sim/stores/workflows/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,10 @@ export function regenerateBlockIds(
blockIdMap.set(oldId, newId)

const oldNormalizedName = normalizeName(block.name)
const newName = uniqueNameFn(block.name, allBlocksForNaming)
const nameConflicts = Object.values(allBlocksForNaming).some(
(existing) => normalizeName(existing.name) === oldNormalizedName
)
const newName = nameConflicts ? uniqueNameFn(block.name, allBlocksForNaming) : block.name
const newNormalizedName = normalizeName(newName)
nameMap.set(oldNormalizedName, newNormalizedName)

Expand Down