Skip to content

Commit ced6412

Browse files
fix(subblock-param-mapping): consolidate resolution of advanced / basic mode params using canonicalParamId (#1274)
* fix(serializer): block's params mapper not running first * fix * fix * revert * add canonicalParamId * fix * fix tests * fix discord * fix condition checking * edit condition check * fix * fix subblock config check * fix * add logging * add more logs * fix * fix * attempt * fix discord * remove unused discord code * mark as required correctly
1 parent 1e14743 commit ced6412

File tree

32 files changed

+240
-1018
lines changed

32 files changed

+240
-1018
lines changed

apps/sim/app/api/tools/discord/channels/route.ts

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ export async function POST(request: Request) {
8585

8686
logger.info(`Fetching all Discord channels for server: ${serverId}`)
8787

88-
// Fetch all channels from Discord API
88+
// Listing guild channels with a bot token is allowed if the bot is in the guild.
89+
// Keep the request, but if unauthorized, return an empty list so the selector doesn't hard fail.
8990
const response = await fetch(`https://discord.com/api/v10/guilds/${serverId}/channels`, {
9091
method: 'GET',
9192
headers: {
@@ -95,20 +96,14 @@ export async function POST(request: Request) {
9596
})
9697

9798
if (!response.ok) {
98-
logger.error('Discord API error:', {
99-
status: response.status,
100-
statusText: response.statusText,
101-
})
102-
103-
let errorMessage
104-
try {
105-
const errorData = await response.json()
106-
logger.error('Error details:', errorData)
107-
errorMessage = errorData.message || `Failed to fetch channels (${response.status})`
108-
} catch (_e) {
109-
errorMessage = `Failed to fetch channels: ${response.status} ${response.statusText}`
110-
}
111-
return NextResponse.json({ error: errorMessage }, { status: response.status })
99+
logger.warn(
100+
'Discord API returned non-OK for channels; returning empty list to avoid UX break',
101+
{
102+
status: response.status,
103+
statusText: response.statusText,
104+
}
105+
)
106+
return NextResponse.json({ channels: [] })
112107
}
113108

114109
const channels = (await response.json()) as DiscordChannel[]

apps/sim/app/api/tools/discord/servers/route.ts

Lines changed: 8 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -64,46 +64,14 @@ export async function POST(request: Request) {
6464
})
6565
}
6666

67-
// Otherwise, fetch all servers the bot is in
68-
logger.info('Fetching all Discord servers')
69-
70-
const response = await fetch('https://discord.com/api/v10/users/@me/guilds', {
71-
method: 'GET',
72-
headers: {
73-
Authorization: `Bot ${botToken}`,
74-
'Content-Type': 'application/json',
75-
},
76-
})
77-
78-
if (!response.ok) {
79-
logger.error('Discord API error:', {
80-
status: response.status,
81-
statusText: response.statusText,
82-
})
83-
84-
let errorMessage
85-
try {
86-
const errorData = await response.json()
87-
logger.error('Error details:', errorData)
88-
errorMessage = errorData.message || `Failed to fetch servers (${response.status})`
89-
} catch (_e) {
90-
errorMessage = `Failed to fetch servers: ${response.status} ${response.statusText}`
91-
}
92-
return NextResponse.json({ error: errorMessage }, { status: response.status })
93-
}
94-
95-
const servers = (await response.json()) as DiscordServer[]
96-
logger.info(`Successfully fetched ${servers.length} servers`)
97-
98-
return NextResponse.json({
99-
servers: servers.map((server: DiscordServer) => ({
100-
id: server.id,
101-
name: server.name,
102-
icon: server.icon
103-
? `https://cdn.discordapp.com/icons/${server.id}/${server.icon}.png`
104-
: null,
105-
})),
106-
})
67+
// Listing guilds via REST requires a user OAuth2 access token with the 'guilds' scope.
68+
// A bot token cannot call /users/@me/guilds and will return 401.
69+
// Since this selector only has a bot token, return an empty list instead of erroring
70+
// and let users provide a Server ID in advanced mode.
71+
logger.info(
72+
'Skipping guild listing: bot token cannot list /users/@me/guilds; returning empty list'
73+
)
74+
return NextResponse.json({ servers: [] })
10775
} catch (error) {
10876
logger.error('Error processing request:', error)
10977
return NextResponse.json(

apps/sim/app/api/workflows/[id]/deploy/route.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,14 +195,31 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
195195

196196
// Process blocks
197197
blocks.forEach((block) => {
198+
const parentId = block.parentId || null
199+
const extent = block.extent || null
200+
const blockData = {
201+
...(block.data || {}),
202+
...(parentId && { parentId }),
203+
...(extent && { extent }),
204+
}
205+
198206
blocksMap[block.id] = {
199207
id: block.id,
200208
type: block.type,
201209
name: block.name,
202210
position: { x: Number(block.positionX), y: Number(block.positionY) },
203-
data: block.data,
211+
data: blockData,
204212
enabled: block.enabled,
205213
subBlocks: block.subBlocks || {},
214+
// Preserve execution-relevant flags so serializer behavior matches manual runs
215+
isWide: block.isWide ?? false,
216+
advancedMode: block.advancedMode ?? false,
217+
triggerMode: block.triggerMode ?? false,
218+
outputs: block.outputs || {},
219+
horizontalHandles: block.horizontalHandles ?? true,
220+
height: Number(block.height || 0),
221+
parentId,
222+
extent,
206223
}
207224
})
208225

apps/sim/app/api/workflows/[id]/status/route.ts

Lines changed: 6 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import { eq } from 'drizzle-orm'
21
import type { NextRequest } from 'next/server'
32
import { createLogger } from '@/lib/logs/console/logger'
3+
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/db-helpers'
44
import { hasWorkflowChanged } from '@/lib/workflows/utils'
55
import { validateWorkflowAccess } from '@/app/api/workflows/middleware'
66
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
7-
import { db } from '@/db'
8-
import { workflowBlocks, workflowEdges, workflowSubflows } from '@/db/schema'
97

108
const logger = createLogger('WorkflowStatusAPI')
119

@@ -24,72 +22,12 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
2422
// Check if the workflow has meaningful changes that would require redeployment
2523
let needsRedeployment = false
2624
if (validation.workflow.isDeployed && validation.workflow.deployedState) {
27-
// Get current state from normalized tables (same logic as deployment API)
28-
const blocks = await db.select().from(workflowBlocks).where(eq(workflowBlocks.workflowId, id))
29-
30-
const edges = await db.select().from(workflowEdges).where(eq(workflowEdges.workflowId, id))
31-
32-
const subflows = await db
33-
.select()
34-
.from(workflowSubflows)
35-
.where(eq(workflowSubflows.workflowId, id))
36-
37-
// Build current state from normalized data
38-
const blocksMap: Record<string, any> = {}
39-
const loops: Record<string, any> = {}
40-
const parallels: Record<string, any> = {}
41-
42-
// Process blocks
43-
blocks.forEach((block) => {
44-
blocksMap[block.id] = {
45-
id: block.id,
46-
type: block.type,
47-
name: block.name,
48-
position: { x: Number(block.positionX), y: Number(block.positionY) },
49-
data: block.data,
50-
enabled: block.enabled,
51-
subBlocks: block.subBlocks || {},
52-
}
53-
})
54-
55-
// Process subflows (loops and parallels)
56-
subflows.forEach((subflow) => {
57-
const config = (subflow.config as any) || {}
58-
if (subflow.type === 'loop') {
59-
loops[subflow.id] = {
60-
id: subflow.id,
61-
nodes: config.nodes || [],
62-
iterations: config.iterations || 1,
63-
loopType: config.loopType || 'for',
64-
forEachItems: config.forEachItems || '',
65-
}
66-
} else if (subflow.type === 'parallel') {
67-
parallels[subflow.id] = {
68-
id: subflow.id,
69-
nodes: config.nodes || [],
70-
count: config.count || 2,
71-
distribution: config.distribution || '',
72-
parallelType: config.parallelType || 'count',
73-
}
74-
}
75-
})
76-
77-
// Convert edges to the expected format
78-
const edgesArray = edges.map((edge) => ({
79-
id: edge.id,
80-
source: edge.sourceBlockId,
81-
target: edge.targetBlockId,
82-
sourceHandle: edge.sourceHandle,
83-
targetHandle: edge.targetHandle,
84-
type: 'default',
85-
data: {},
86-
}))
87-
25+
const normalizedData = await loadWorkflowFromNormalizedTables(id)
8826
const currentState = {
89-
blocks: blocksMap,
90-
edges: edgesArray,
91-
loops,
92-
parallels,
27+
blocks: normalizedData?.blocks || {},
28+
edges: normalizedData?.edges || [],
29+
loops: normalizedData?.loops || {},
30+
parallels: normalizedData?.parallels || {},
9331
lastSaved: Date.now(),
9432
}
9533

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/control-bar.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
useKeyboardShortcuts,
4646
} from '@/app/workspace/[workspaceId]/w/hooks/use-keyboard-shortcuts'
4747
import { useFolderStore } from '@/stores/folders/store'
48+
import { useOperationQueueStore } from '@/stores/operation-queue/store'
4849
import { usePanelStore } from '@/stores/panel/store'
4950
import { useGeneralStore } from '@/stores/settings/general/store'
5051
import { useSubscriptionStore } from '@/stores/subscription/store'
@@ -258,17 +259,23 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
258259

259260
// Get current store state for change detection
260261
const currentBlocks = useWorkflowStore((state) => state.blocks)
262+
const currentEdges = useWorkflowStore((state) => state.edges)
261263
const subBlockValues = useSubBlockStore((state) =>
262264
activeWorkflowId ? state.workflowValues[activeWorkflowId] : null
263265
)
264266

265267
useEffect(() => {
268+
// Avoid off-by-one false positives: wait until operation queue is idle
269+
const { operations, isProcessing } = useOperationQueueStore.getState()
270+
const hasPendingOps =
271+
isProcessing || operations.some((op) => op.status === 'pending' || op.status === 'processing')
272+
266273
if (!activeWorkflowId || !deployedState) {
267274
setChangeDetected(false)
268275
return
269276
}
270277

271-
if (isLoadingDeployedState) {
278+
if (isLoadingDeployedState || hasPendingOps) {
272279
return
273280
}
274281

@@ -291,7 +298,16 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
291298
}
292299

293300
checkForChanges()
294-
}, [activeWorkflowId, deployedState, currentBlocks, subBlockValues, isLoadingDeployedState])
301+
}, [
302+
activeWorkflowId,
303+
deployedState,
304+
currentBlocks,
305+
currentEdges,
306+
subBlockValues,
307+
isLoadingDeployedState,
308+
useOperationQueueStore.getState().isProcessing,
309+
useOperationQueueStore.getState().operations.length,
310+
])
295311

296312
useEffect(() => {
297313
if (session?.user?.id && !isRegistryLoading) {

0 commit comments

Comments
 (0)