Skip to content

Commit 427a530

Browse files
committed
minor codec fix
1 parent 748473c commit 427a530

2 files changed

Lines changed: 124 additions & 1 deletion

File tree

apps/sim/lib/workflows/search-replace/replacements.test.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,32 @@ describe('buildWorkflowSearchReplacePlan', () => {
180180
])
181181
})
182182

183+
it('replaces duplicate structured resources with blank comma segments consistently', () => {
184+
const workflow = createSearchReplaceWorkflowFixture()
185+
workflow.blocks['knowledge-1'].subBlocks.knowledgeBaseIds.value = 'kb-old,,kb-second,kb-old'
186+
187+
const matches = indexWorkflowSearchMatches({
188+
workflow,
189+
query: 'kb-old',
190+
mode: 'resource',
191+
blockConfigs: SEARCH_REPLACE_BLOCK_CONFIGS,
192+
}).filter((match) => match.kind === 'knowledge-base')
193+
194+
const plan = buildWorkflowSearchReplacePlan({
195+
blocks: workflow.blocks,
196+
matches,
197+
selectedMatchIds: new Set([matches[1].id]),
198+
defaultReplacement: 'kb-new',
199+
resourceReplacementOptions: [
200+
{ kind: 'knowledge-base', value: 'kb-new', label: 'New Knowledge Base' },
201+
],
202+
})
203+
204+
expect(plan.conflicts).toEqual([])
205+
expect(plan.updates).toHaveLength(1)
206+
expect(plan.updates[0].nextValue).toBe('kb-old,,kb-second,kb-new')
207+
})
208+
183209
it('replaces all compatible knowledge base references across blocks', () => {
184210
const workflow = createSearchReplaceWorkflowFixture()
185211
workflow.blocks['knowledge-2'] = {
@@ -588,6 +614,101 @@ describe('buildWorkflowSearchReplacePlan', () => {
588614
])
589615
})
590616

617+
it('conflicts when a selected duplicate file occurrence becomes a single file object', () => {
618+
const workflow = createSearchReplaceWorkflowFixture()
619+
const firstFile = {
620+
name: 'first.pdf',
621+
key: 'file-key-old',
622+
path: '/first.pdf',
623+
size: 12,
624+
type: 'application/pdf',
625+
}
626+
const secondFile = {
627+
name: 'second.pdf',
628+
key: 'file-key-old',
629+
path: '/second.pdf',
630+
size: 14,
631+
type: 'application/pdf',
632+
}
633+
workflow.blocks['tool-input-1'] = {
634+
id: 'tool-input-1',
635+
type: 'custom',
636+
name: 'Tool Input Block',
637+
position: { x: 0, y: 0 },
638+
enabled: true,
639+
outputs: {},
640+
subBlocks: {
641+
tools: {
642+
id: 'tools',
643+
type: 'tool-input',
644+
value: [
645+
{
646+
type: 'slack',
647+
toolId: 'slack_message',
648+
operation: 'send',
649+
title: 'Slack message',
650+
params: {
651+
authMethod: 'oauth',
652+
credential: 'slack-credential',
653+
text: 'message with files',
654+
attachmentFiles: [firstFile, secondFile],
655+
},
656+
},
657+
],
658+
},
659+
},
660+
}
661+
662+
const matches = indexWorkflowSearchMatches({
663+
workflow,
664+
query: 'file-key-old',
665+
mode: 'resource',
666+
blockConfigs: {
667+
...SEARCH_REPLACE_BLOCK_CONFIGS,
668+
custom: {
669+
subBlocks: [{ id: 'tools', title: 'Tools', type: 'tool-input' }],
670+
},
671+
},
672+
}).filter((match) => match.kind === 'file')
673+
674+
workflow.blocks['tool-input-1'].subBlocks.tools.value = [
675+
{
676+
type: 'slack',
677+
toolId: 'slack_message',
678+
operation: 'send',
679+
title: 'Slack message',
680+
params: {
681+
authMethod: 'oauth',
682+
credential: 'slack-credential',
683+
text: 'message with files',
684+
attachmentFiles: firstFile,
685+
},
686+
},
687+
]
688+
689+
const replacementFile = {
690+
name: 'replacement.pdf',
691+
key: 'file-key-new',
692+
path: '/replacement.pdf',
693+
size: 24,
694+
type: 'application/pdf',
695+
}
696+
const plan = buildWorkflowSearchReplacePlan({
697+
blocks: workflow.blocks,
698+
matches,
699+
selectedMatchIds: new Set([matches[1].id]),
700+
defaultReplacement: JSON.stringify(replacementFile),
701+
resourceReplacementOptions: [
702+
{ kind: 'file', value: JSON.stringify(replacementFile), label: replacementFile.name },
703+
],
704+
})
705+
706+
expect(plan.updates).toEqual([])
707+
expect(plan.conflicts).toEqual([
708+
{ matchId: matches[1].id, reason: 'Target resource changed since search' },
709+
])
710+
})
711+
591712
it('clears nested tool-input dependents when replacing a parent resource', () => {
592713
const workflow = createSearchReplaceWorkflowFixture()
593714
workflow.blocks['tool-input-1'] = {

apps/sim/lib/workflows/search-replace/resources/registry.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ function replaceCommaResourceValue(
105105
let replaced = false
106106

107107
const shouldReplace = (item: string) => {
108+
if (!item) return false
108109
const currentOccurrenceIndex = occurrenceIndex
109110
occurrenceIndex += 1
110111
if (item !== rawValue) return false
@@ -261,10 +262,11 @@ const fileUploadResourceCodec: WorkflowSearchResourceCodec = {
261262
}
262263

263264
const result = replaceItem(parsed.value)
264-
if (!result.success || !parsed.serialized) return result
265+
if (!result.success) return result
265266
if (targetOccurrenceIndex !== undefined && !replaced) {
266267
return { success: false, reason: 'Target resource changed since search' }
267268
}
269+
if (!parsed.serialized) return result
268270
return { success: true, nextValue: JSON.stringify(result.nextValue) }
269271
},
270272
}

0 commit comments

Comments
 (0)