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
7 changes: 7 additions & 0 deletions apps/sim/app/api/workflows/[id]/deploy/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,18 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
const { loadWorkflowFromNormalizedTables } = await import('@/lib/workflows/persistence/utils')
const normalizedData = await loadWorkflowFromNormalizedTables(id)
if (normalizedData) {
const [workflowRecord] = await db
.select({ variables: workflow.variables })
.from(workflow)
.where(eq(workflow.id, id))
.limit(1)

const currentState = {
blocks: normalizedData.blocks,
edges: normalizedData.edges,
loops: normalizedData.loops,
parallels: normalizedData.parallels,
variables: workflowRecord?.variables || {},
}
const { hasWorkflowChanged } = await import('@/lib/workflows/comparison')
needsRedeployment = hasWorkflowChanged(currentState as any, active.state as any)
Expand Down
19 changes: 16 additions & 3 deletions apps/sim/app/api/workflows/[id]/execute/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
loops: Record<string, any>
parallels: Record<string, any>
deploymentVersionId?: string
variables?: Record<string, any>
} | null = null

let processedInput = input
Expand All @@ -327,6 +328,11 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
: await loadDeployedWorkflowState(workflowId)

if (workflowData) {
const deployedVariables =
!shouldUseDraftState && 'variables' in workflowData
? (workflowData as any).variables
: undefined

cachedWorkflowData = {
blocks: workflowData.blocks,
edges: workflowData.edges,
Expand All @@ -336,6 +342,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
!shouldUseDraftState && 'deploymentVersionId' in workflowData
? (workflowData.deploymentVersionId as string)
: undefined,
variables: deployedVariables,
}

const serializedWorkflow = new Serializer().serializeWorkflow(
Expand Down Expand Up @@ -405,11 +412,13 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
workflowStateOverride: effectiveWorkflowStateOverride,
}

const executionVariables = cachedWorkflowData?.variables ?? workflow.variables ?? {}

const snapshot = new ExecutionSnapshot(
metadata,
workflow,
processedInput,
workflow.variables || {},
executionVariables,
selectedOutputs
)

Expand Down Expand Up @@ -471,14 +480,16 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
selectedOutputs,
cachedWorkflowData?.blocks || {}
)
const streamVariables = cachedWorkflowData?.variables ?? (workflow as any).variables

const stream = await createStreamingResponse({
requestId,
workflow: {
id: workflow.id,
userId: actorUserId,
workspaceId,
isDeployed: workflow.isDeployed,
variables: (workflow as any).variables,
variables: streamVariables,
},
input: processedInput,
executingUserId: actorUserId,
Expand Down Expand Up @@ -675,11 +686,13 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
workflowStateOverride: effectiveWorkflowStateOverride,
}

const sseExecutionVariables = cachedWorkflowData?.variables ?? workflow.variables ?? {}

const snapshot = new ExecutionSnapshot(
metadata,
workflow,
processedInput,
workflow.variables || {},
sseExecutionVariables,
selectedOutputs
)

Expand Down
14 changes: 8 additions & 6 deletions apps/sim/app/api/workflows/[id]/status/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { db, workflowDeploymentVersion } from '@sim/db'
import { db, workflow, workflowDeploymentVersion } from '@sim/db'
import { and, desc, eq } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { generateRequestId } from '@/lib/core/utils/request'
Expand All @@ -22,17 +22,12 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
return createErrorResponse(validation.error.message, validation.error.status)
}

// Check if the workflow has meaningful changes that would require redeployment
let needsRedeployment = false

if (validation.workflow.isDeployed) {
// Get current state from normalized tables (same logic as deployment API)
// Load current state from normalized tables using centralized helper
const normalizedData = await loadWorkflowFromNormalizedTables(id)

if (!normalizedData) {
// Workflow exists but has no blocks in normalized tables (empty workflow or not migrated)
// This is valid state - return success with no redeployment needed
return createSuccessResponse({
isDeployed: validation.workflow.isDeployed,
deployedAt: validation.workflow.deployedAt,
Expand All @@ -41,11 +36,18 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
})
}

const [workflowRecord] = await db
.select({ variables: workflow.variables })
.from(workflow)
.where(eq(workflow.id, id))
.limit(1)

const currentState = {
blocks: normalizedData.blocks,
edges: normalizedData.edges,
loops: normalizedData.loops,
parallels: normalizedData.parallels,
variables: workflowRecord?.variables || {},
lastSaved: Date.now(),
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ interface DeployModalProps {
workflowId: string | null
isDeployed: boolean
needsRedeployment: boolean
setNeedsRedeployment: (value: boolean) => void
deployedState: WorkflowState
isLoadingDeployedState: boolean
refetchDeployedState: () => Promise<void>
Expand All @@ -58,7 +57,6 @@ export function DeployModal({
workflowId,
isDeployed: isDeployedProp,
needsRedeployment,
setNeedsRedeployment,
deployedState,
isLoadingDeployedState,
refetchDeployedState,
Expand Down Expand Up @@ -229,7 +227,6 @@ export function DeployModal({

setDeploymentStatus(workflowId, isDeployedStatus, deployedAtTime, apiKeyLabel)

setNeedsRedeployment(false)
if (workflowId) {
useWorkflowRegistry.getState().setWorkflowNeedsRedeployment(workflowId, false)
}
Expand Down Expand Up @@ -453,7 +450,6 @@ export function DeployModal({
getApiKeyLabel(apiKey)
)

setNeedsRedeployment(false)
if (workflowId) {
useWorkflowRegistry.getState().setWorkflowNeedsRedeployment(workflowId, false)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ export function Deploy({ activeWorkflowId, userPermissions, className }: DeployP
isRegistryLoading,
})

// Detect changes between current and deployed state
const { changeDetected, setChangeDetected } = useChangeDetection({
const { changeDetected } = useChangeDetection({
workflowId: activeWorkflowId,
deployedState,
isLoadingDeployedState,
Expand Down Expand Up @@ -136,7 +135,6 @@ export function Deploy({ activeWorkflowId, userPermissions, className }: DeployP
workflowId={activeWorkflowId}
isDeployed={isDeployed}
needsRedeployment={changeDetected}
setNeedsRedeployment={setChangeDetected}
deployedState={deployedState!}
isLoadingDeployedState={isLoadingDeployedState}
refetchDeployedState={refetchWithErrorHandling}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useMemo } from 'react'
import { hasWorkflowChanged } from '@/lib/workflows/comparison'
import { useDebounce } from '@/hooks/use-debounce'
import { useVariablesStore } from '@/stores/panel/variables/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
import type { WorkflowState } from '@/stores/workflows/workflow/types'
Expand All @@ -27,8 +28,18 @@ export function useChangeDetection({
const subBlockValues = useSubBlockStore((state) =>
workflowId ? state.workflowValues[workflowId] : null
)
const allVariables = useVariablesStore((state) => state.variables)
const workflowVariables = useMemo(() => {
if (!workflowId) return {}
const vars: Record<string, any> = {}
for (const [id, variable] of Object.entries(allVariables)) {
if (variable.workflowId === workflowId) {
vars[id] = variable
}
}
return vars
}, [workflowId, allVariables])

// Build current state with subblock values merged into blocks
const currentState = useMemo((): WorkflowState | null => {
if (!workflowId) return null

Expand All @@ -37,12 +48,10 @@ export function useChangeDetection({
const blockSubValues = subBlockValues?.[blockId] || {}
const subBlocks: Record<string, any> = {}

// Merge subblock values into the block's subBlocks structure
for (const [subId, value] of Object.entries(blockSubValues)) {
subBlocks[subId] = { value }
}

// Also include existing subBlocks from the block itself
if (block.subBlocks) {
for (const [subId, subBlock] of Object.entries(block.subBlocks)) {
if (!subBlocks[subId]) {
Expand All @@ -64,24 +73,18 @@ export function useChangeDetection({
edges,
loops,
parallels,
}
}, [workflowId, blocks, edges, loops, parallels, subBlockValues])
variables: workflowVariables,
} as WorkflowState & { variables: Record<string, any> }
}, [workflowId, blocks, edges, loops, parallels, subBlockValues, workflowVariables])

// Compute change detection with debouncing for performance
const rawChangeDetected = useMemo(() => {
if (!currentState || !deployedState || isLoadingDeployedState) {
return false
}
return hasWorkflowChanged(currentState, deployedState)
}, [currentState, deployedState, isLoadingDeployedState])

// Debounce to avoid UI flicker during rapid edits
const changeDetected = useDebounce(rawChangeDetected, 300)

const setChangeDetected = () => {
// No-op: change detection is now computed, not stateful
// Kept for API compatibility
}

return { changeDetected, setChangeDetected }
return { changeDetected }
}
Loading
Loading