refactor(store): remove unused workflow store functions

Remove redundant functions superseded by collaborative workflow patterns:
- duplicateBlock (superseded by collaborative paste flow)
- toggleBlockAdvancedMode (superseded by setBlockAdvancedMode)
- updateLoopCollection (redundant wrapper)
- setBlockTriggerMode (unused)
- generateLoopBlocks/generateParallelBlocks methods (called directly as utils)

Also removes ~160 lines of related tests and cleans up unused imports.
This commit is contained in:
waleed
2026-01-27 13:20:59 -08:00
parent fe4fd47b9d
commit f2ca90ae6f
4 changed files with 55 additions and 376 deletions

View File

@@ -517,8 +517,7 @@ const WorkflowContent = React.memo(() => {
})
}, [edges, isShowingDiff, isDiffReady, diffAnalysis, blocks])
const { userPermissions, workspacePermissions, permissionsError } =
useWorkspacePermissionsContext()
const { userPermissions } = useWorkspacePermissionsContext()
/** Returns read-only permissions when viewing snapshot, otherwise user permissions. */
const effectivePermissions = useMemo(() => {
@@ -754,25 +753,6 @@ const WorkflowContent = React.memo(() => {
[isErrorConnectionDrag]
)
/** Logs permission loading results for debugging. */
useEffect(() => {
if (permissionsError) {
logger.error('Failed to load workspace permissions', {
workspaceId,
error: permissionsError,
})
} else if (workspacePermissions) {
logger.info('Workspace permissions loaded in workflow', {
workspaceId,
userCount: workspacePermissions.total,
permissions: workspacePermissions.users.map((u) => ({
email: u.email,
permissions: u.permissionType,
})),
})
}
}, [workspacePermissions, permissionsError, workspaceId])
const updateNodeParent = useCallback(
(nodeId: string, newParentId: string | null, affectedEdges: any[] = []) => {
const node = getNodes().find((n: any) => n.id === nodeId)

View File

@@ -22,8 +22,6 @@ import {
WorkflowBuilder,
} from '@sim/testing'
import { beforeEach, describe, expect, it } from 'vitest'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
describe('workflow store', () => {
@@ -365,30 +363,6 @@ describe('workflow store', () => {
})
})
describe('duplicateBlock', () => {
it('should duplicate a block', () => {
const { addBlock, duplicateBlock } = useWorkflowStore.getState()
addBlock('original', 'agent', 'Original Agent', { x: 0, y: 0 })
duplicateBlock('original')
const { blocks } = useWorkflowStore.getState()
const blockIds = Object.keys(blocks)
expect(blockIds.length).toBe(2)
const duplicatedId = blockIds.find((id) => id !== 'original')
expect(duplicatedId).toBeDefined()
if (duplicatedId) {
expect(blocks[duplicatedId].type).toBe('agent')
expect(blocks[duplicatedId].name).toContain('Original Agent')
expect(blocks[duplicatedId].position.x).not.toBe(0)
}
})
})
describe('batchUpdatePositions', () => {
it('should update block position', () => {
const { addBlock, batchUpdatePositions } = useWorkflowStore.getState()
@@ -452,29 +426,6 @@ describe('workflow store', () => {
expect(state.loops.loop1.forEachItems).toBe('["a", "b", "c"]')
})
it('should regenerate loops when updateLoopCollection is called', () => {
const { addBlock, updateLoopCollection } = useWorkflowStore.getState()
addBlock(
'loop1',
'loop',
'Test Loop',
{ x: 0, y: 0 },
{
loopType: 'forEach',
collection: '["item1", "item2"]',
}
)
updateLoopCollection('loop1', '["item1", "item2", "item3"]')
const state = useWorkflowStore.getState()
expect(state.blocks.loop1?.data?.collection).toBe('["item1", "item2", "item3"]')
expect(state.loops.loop1).toBeDefined()
expect(state.loops.loop1.forEachItems).toBe('["item1", "item2", "item3"]')
})
it('should clamp loop count between 1 and 1000', () => {
const { addBlock, updateLoopCount } = useWorkflowStore.getState()
@@ -599,118 +550,6 @@ describe('workflow store', () => {
})
})
describe('mode switching', () => {
it('should toggle advanced mode on a block', () => {
const { addBlock, toggleBlockAdvancedMode } = useWorkflowStore.getState()
addBlock('agent1', 'agent', 'Test Agent', { x: 0, y: 0 })
let state = useWorkflowStore.getState()
expect(state.blocks.agent1?.advancedMode).toBe(false)
toggleBlockAdvancedMode('agent1')
state = useWorkflowStore.getState()
expect(state.blocks.agent1?.advancedMode).toBe(true)
toggleBlockAdvancedMode('agent1')
state = useWorkflowStore.getState()
expect(state.blocks.agent1?.advancedMode).toBe(false)
})
it('should preserve systemPrompt and userPrompt when switching modes', () => {
const { addBlock, toggleBlockAdvancedMode } = useWorkflowStore.getState()
const { setState: setSubBlockState } = useSubBlockStore
useWorkflowRegistry.setState({ activeWorkflowId: 'test-workflow' })
addBlock('agent1', 'agent', 'Test Agent', { x: 0, y: 0 })
setSubBlockState({
workflowValues: {
'test-workflow': {
agent1: {
systemPrompt: 'You are a helpful assistant',
userPrompt: 'Hello, how are you?',
},
},
},
})
toggleBlockAdvancedMode('agent1')
let subBlockState = useSubBlockStore.getState()
expect(subBlockState.workflowValues['test-workflow'].agent1.systemPrompt).toBe(
'You are a helpful assistant'
)
expect(subBlockState.workflowValues['test-workflow'].agent1.userPrompt).toBe(
'Hello, how are you?'
)
toggleBlockAdvancedMode('agent1')
subBlockState = useSubBlockStore.getState()
expect(subBlockState.workflowValues['test-workflow'].agent1.systemPrompt).toBe(
'You are a helpful assistant'
)
expect(subBlockState.workflowValues['test-workflow'].agent1.userPrompt).toBe(
'Hello, how are you?'
)
})
it('should preserve memories when switching from advanced to basic mode', () => {
const { addBlock, toggleBlockAdvancedMode } = useWorkflowStore.getState()
const { setState: setSubBlockState } = useSubBlockStore
useWorkflowRegistry.setState({ activeWorkflowId: 'test-workflow' })
addBlock('agent1', 'agent', 'Test Agent', { x: 0, y: 0 })
toggleBlockAdvancedMode('agent1')
setSubBlockState({
workflowValues: {
'test-workflow': {
agent1: {
systemPrompt: 'You are a helpful assistant',
userPrompt: 'What did we discuss?',
memories: [
{ role: 'user', content: 'My name is John' },
{ role: 'assistant', content: 'Nice to meet you, John!' },
],
},
},
},
})
toggleBlockAdvancedMode('agent1')
const subBlockState = useSubBlockStore.getState()
expect(subBlockState.workflowValues['test-workflow'].agent1.systemPrompt).toBe(
'You are a helpful assistant'
)
expect(subBlockState.workflowValues['test-workflow'].agent1.userPrompt).toBe(
'What did we discuss?'
)
expect(subBlockState.workflowValues['test-workflow'].agent1.memories).toEqual([
{ role: 'user', content: 'My name is John' },
{ role: 'assistant', content: 'Nice to meet you, John!' },
])
})
it('should handle mode switching when no subblock values exist', () => {
const { addBlock, toggleBlockAdvancedMode } = useWorkflowStore.getState()
useWorkflowRegistry.setState({ activeWorkflowId: 'test-workflow' })
addBlock('agent1', 'agent', 'Test Agent', { x: 0, y: 0 })
expect(useWorkflowStore.getState().blocks.agent1?.advancedMode).toBe(false)
expect(() => toggleBlockAdvancedMode('agent1')).not.toThrow()
const state = useWorkflowStore.getState()
expect(state.blocks.agent1?.advancedMode).toBe(true)
})
it('should not throw when toggling non-existent block', () => {
const { toggleBlockAdvancedMode } = useWorkflowStore.getState()
expect(() => toggleBlockAdvancedMode('non-existent')).not.toThrow()
})
})
describe('workflow state management', () => {
it('should work with WorkflowBuilder for complex setups', () => {
const workflowState = WorkflowBuilder.linear(3).build()

View File

@@ -2,20 +2,15 @@ import { createLogger } from '@sim/logger'
import type { Edge } from 'reactflow'
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
import { DEFAULT_DUPLICATE_OFFSET } from '@/lib/workflows/autolayout/constants'
import { getBlockOutputs } from '@/lib/workflows/blocks/block-outputs'
import { getBlock } from '@/blocks'
import type { SubBlockConfig } from '@/blocks/types'
import { normalizeName, RESERVED_BLOCK_NAMES } from '@/executor/constants'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import {
filterNewEdges,
filterValidEdges,
getUniqueBlockName,
mergeSubblockState,
} from '@/stores/workflows/utils'
import { filterNewEdges, filterValidEdges } from '@/stores/workflows/utils'
import type {
BlockState,
Position,
SubBlockState,
WorkflowState,
@@ -139,30 +134,30 @@ export const useWorkflowStore = create<WorkflowStore>()(
...(parentId && { parentId, extent: extent || 'parent' }),
}
const newState = {
blocks: {
...get().blocks,
[id]: {
id,
type,
name,
position,
subBlocks: {},
outputs: {},
enabled: blockProperties?.enabled ?? true,
horizontalHandles: blockProperties?.horizontalHandles ?? true,
advancedMode: blockProperties?.advancedMode ?? false,
triggerMode: blockProperties?.triggerMode ?? false,
height: blockProperties?.height ?? 0,
data: nodeData,
},
const newBlocks = {
...get().blocks,
[id]: {
id,
type,
name,
position,
subBlocks: {},
outputs: {},
enabled: blockProperties?.enabled ?? true,
horizontalHandles: blockProperties?.horizontalHandles ?? true,
advancedMode: blockProperties?.advancedMode ?? false,
triggerMode: blockProperties?.triggerMode ?? false,
height: blockProperties?.height ?? 0,
data: nodeData,
},
edges: [...get().edges],
loops: get().generateLoopBlocks(),
parallels: get().generateParallelBlocks(),
}
set(newState)
set({
blocks: newBlocks,
edges: [...get().edges],
loops: generateLoopBlocks(newBlocks),
parallels: generateParallelBlocks(newBlocks),
})
get().updateLastSaved()
return
}
@@ -215,31 +210,31 @@ export const useWorkflowStore = create<WorkflowStore>()(
const triggerMode = blockProperties?.triggerMode ?? false
const outputs = getBlockOutputs(type, subBlocks, triggerMode)
const newState = {
blocks: {
...get().blocks,
[id]: {
id,
type,
name,
position,
subBlocks,
outputs,
enabled: blockProperties?.enabled ?? true,
horizontalHandles: blockProperties?.horizontalHandles ?? true,
advancedMode: blockProperties?.advancedMode ?? false,
triggerMode: triggerMode,
height: blockProperties?.height ?? 0,
layout: {},
data: nodeData,
},
const newBlocks = {
...get().blocks,
[id]: {
id,
type,
name,
position,
subBlocks,
outputs,
enabled: blockProperties?.enabled ?? true,
horizontalHandles: blockProperties?.horizontalHandles ?? true,
advancedMode: blockProperties?.advancedMode ?? false,
triggerMode: triggerMode,
height: blockProperties?.height ?? 0,
layout: {},
data: nodeData,
},
edges: [...get().edges],
loops: get().generateLoopBlocks(),
parallels: get().generateParallelBlocks(),
}
set(newState)
set({
blocks: newBlocks,
edges: [...get().edges],
loops: generateLoopBlocks(newBlocks),
parallels: generateParallelBlocks(newBlocks),
})
get().updateLastSaved()
},
@@ -451,20 +446,23 @@ export const useWorkflowStore = create<WorkflowStore>()(
// Clean up orphaned nodes - blocks whose parent was removed but weren't descendants
// This can happen in edge cases (e.g., data inconsistency, external modifications)
const remainingBlockIds = new Set(Object.keys(newBlocks))
const CONTAINER_OFFSET = { x: 16, y: 50 + 16 } // leftPadding, headerHeight + topPadding
Object.entries(newBlocks).forEach(([blockId, block]) => {
const parentId = block.data?.parentId
if (parentId && !remainingBlockIds.has(parentId)) {
// Parent was removed - convert to absolute position and clear parentId
// Calculate absolute position by traversing up the (now-deleted) parent chain
// Child positions are relative to container content area (after header + padding)
let absoluteX = block.position.x
let absoluteY = block.position.y
// Try to get parent's position from original blocks before deletion
// Traverse up the parent chain, adding position + container offset for each level
let currentParentId: string | undefined = parentId
while (currentParentId && currentBlocks[currentParentId]) {
const parent = currentBlocks[currentParentId]
absoluteX += parent.position.x
absoluteY += parent.position.y
while (currentParentId) {
const parent: BlockState | undefined = currentBlocks[currentParentId]
if (!parent) break
absoluteX += parent.position.x + CONTAINER_OFFSET.x
absoluteY += parent.position.y + CONTAINER_OFFSET.y
currentParentId = parent.data?.parentId
}
@@ -663,66 +661,6 @@ export const useWorkflowStore = create<WorkflowStore>()(
get().updateLastSaved()
},
duplicateBlock: (id: string) => {
const block = get().blocks[id]
if (!block) return
const newId = crypto.randomUUID()
const offsetPosition = {
x: block.position.x + DEFAULT_DUPLICATE_OFFSET.x,
y: block.position.y + DEFAULT_DUPLICATE_OFFSET.y,
}
const newName = getUniqueBlockName(block.name, get().blocks)
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
const mergedBlock = mergeSubblockState(get().blocks, activeWorkflowId || undefined, id)[id]
const newSubBlocks = Object.entries(mergedBlock.subBlocks).reduce(
(acc, [subId, subBlock]) => ({
...acc,
[subId]: {
...subBlock,
value: JSON.parse(JSON.stringify(subBlock.value)),
},
}),
{}
)
const newState = {
blocks: {
...get().blocks,
[newId]: {
...block,
id: newId,
name: newName,
position: offsetPosition,
subBlocks: newSubBlocks,
},
},
edges: [...get().edges],
loops: get().generateLoopBlocks(),
parallels: get().generateParallelBlocks(),
}
if (activeWorkflowId) {
const subBlockValues =
useSubBlockStore.getState().workflowValues[activeWorkflowId]?.[id] || {}
useSubBlockStore.setState((state) => ({
workflowValues: {
...state.workflowValues,
[activeWorkflowId]: {
...state.workflowValues[activeWorkflowId],
[newId]: JSON.parse(JSON.stringify(subBlockValues)),
},
},
}))
}
set(newState)
get().updateLastSaved()
},
setBlockHandles: (id: string, horizontalHandles: boolean) => {
const block = get().blocks[id]
if (!block || block.horizontalHandles === horizontalHandles) return
@@ -918,27 +856,10 @@ export const useWorkflowStore = create<WorkflowStore>()(
get().updateLastSaved()
},
setBlockTriggerMode: (id: string, triggerMode: boolean) => {
set((state) => ({
blocks: {
...state.blocks,
[id]: {
...state.blocks[id],
triggerMode,
},
},
edges: [...state.edges],
loops: { ...state.loops },
}))
get().updateLastSaved()
// Note: Socket.IO handles real-time sync automatically
},
updateBlockLayoutMetrics: (id: string, dimensions: { width: number; height: number }) => {
set((state) => {
const block = state.blocks[id]
if (!block) {
logger.warn(`Cannot update layout metrics: Block ${id} not found in workflow store`)
return state
}
@@ -960,7 +881,6 @@ export const useWorkflowStore = create<WorkflowStore>()(
}
})
get().updateLastSaved()
// No sync needed for layout changes, just visual
},
updateLoopCount: (loopId: string, count: number) =>
@@ -1078,30 +998,6 @@ export const useWorkflowStore = create<WorkflowStore>()(
}
}),
updateLoopCollection: (loopId: string, collection: string) => {
const store = get()
const block = store.blocks[loopId]
if (!block || block.type !== 'loop') return
const loopType = block.data?.loopType || 'for'
if (loopType === 'while') {
store.setLoopWhileCondition(loopId, collection)
} else if (loopType === 'doWhile') {
store.setLoopDoWhileCondition(loopId, collection)
} else if (loopType === 'forEach') {
store.setLoopForEachItems(loopId, collection)
} else {
// Default to forEach-style storage for backward compatibility
store.setLoopForEachItems(loopId, collection)
}
},
// Function to convert UI loop blocks to execution format
generateLoopBlocks: () => {
return generateLoopBlocks(get().blocks)
},
triggerUpdate: () => {
set((state) => ({
...state,
@@ -1189,28 +1085,6 @@ export const useWorkflowStore = create<WorkflowStore>()(
}
},
toggleBlockAdvancedMode: (id: string) => {
const block = get().blocks[id]
if (!block) return
const newState = {
blocks: {
...get().blocks,
[id]: {
...block,
advancedMode: !block.advancedMode,
},
},
edges: [...get().edges],
loops: { ...get().loops },
}
set(newState)
get().triggerUpdate()
// Note: Socket.IO handles real-time sync automatically
},
// Parallel block methods implementation
updateParallelCount: (parallelId: string, count: number) => {
const block = get().blocks[parallelId]
@@ -1236,7 +1110,6 @@ export const useWorkflowStore = create<WorkflowStore>()(
set(newState)
get().updateLastSaved()
// Note: Socket.IO handles real-time sync automatically
},
updateParallelCollection: (parallelId: string, collection: string) => {
@@ -1263,7 +1136,6 @@ export const useWorkflowStore = create<WorkflowStore>()(
set(newState)
get().updateLastSaved()
// Note: Socket.IO handles real-time sync automatically
},
updateParallelType: (parallelId: string, parallelType: 'count' | 'collection') => {
@@ -1290,12 +1162,6 @@ export const useWorkflowStore = create<WorkflowStore>()(
set(newState)
get().updateLastSaved()
// Note: Socket.IO handles real-time sync automatically
},
// Function to convert UI parallel blocks to execution format
generateParallelBlocks: () => {
return generateParallelBlocks(get().blocks)
},
setDragStartPosition: (position) => {

View File

@@ -214,7 +214,6 @@ export interface WorkflowActions {
clear: () => Partial<WorkflowState>
updateLastSaved: () => void
setBlockEnabled: (id: string, enabled: boolean) => void
duplicateBlock: (id: string) => void
setBlockHandles: (id: string, horizontalHandles: boolean) => void
updateBlockName: (
id: string,
@@ -225,23 +224,18 @@ export interface WorkflowActions {
}
setBlockAdvancedMode: (id: string, advancedMode: boolean) => void
setBlockCanonicalMode: (id: string, canonicalId: string, mode: 'basic' | 'advanced') => void
setBlockTriggerMode: (id: string, triggerMode: boolean) => void
updateBlockLayoutMetrics: (id: string, dimensions: { width: number; height: number }) => void
triggerUpdate: () => void
updateLoopCount: (loopId: string, count: number) => void
updateLoopType: (loopId: string, loopType: 'for' | 'forEach' | 'while' | 'doWhile') => void
updateLoopCollection: (loopId: string, collection: string) => void
setLoopForEachItems: (loopId: string, items: any) => void
setLoopWhileCondition: (loopId: string, condition: string) => void
setLoopDoWhileCondition: (loopId: string, condition: string) => void
updateParallelCount: (parallelId: string, count: number) => void
updateParallelCollection: (parallelId: string, collection: string) => void
updateParallelType: (parallelId: string, parallelType: 'count' | 'collection') => void
generateLoopBlocks: () => Record<string, Loop>
generateParallelBlocks: () => Record<string, Parallel>
setNeedsRedeploymentFlag: (needsRedeployment: boolean) => void
revertToDeployedState: (deployedState: WorkflowState) => void
toggleBlockAdvancedMode: (id: string) => void
setDragStartPosition: (position: DragStartPosition | null) => void
getDragStartPosition: () => DragStartPosition | null
getWorkflowState: () => WorkflowState