Skip to content

Commit 928581f

Browse files
feat(deployments): make deployed state source of truth for non-manual executions + versioning (#1242)
* feat(deployments): make deployed state source of truth for non-manual executions + versioning * fix lint * fix test * add script to migrate to deployed vesions * fix deployed chat * address greptile commands * Remove 84th migration files to prepare for regeneration * fix script + update migration * fix script * cleanup typing * use shared helper * fix tests * fix duplicate route * revert migrations prep * add migration back * add workflow in workflow block func * fix UI * fix lint * make migration idempotent * remove migrations * add migrations back' * adjust script to reuse helper * add test webhook URL functionality * consolidate test URL + prod URL code for webhooks * fixes * update trigger config save with correct dependencies * make frozen canvas respect trigger mode * chore(db): remove latest migration 0088, snapshot, journal entry; delete migrate-deployment-versions script * separate parent id cleanup migration * chore(db): remove 0089 migration, snapshot, and prune journal entry * chore(db): generate 0090 migration after removing 0089 and merging staging * fix * chore(db): remove 0090 migration, snapshot, and prune journal entry * chore(db): generate 0091 migration after merging staging and restoring idempotency flow * fix some imports * remove dead code * fix tests * remove comment * working test url functionality restored * works * some styling changes * make deploy text accurate * chore(db): remove latest migration 0091 and snapshot; update journal before merging staging * db(migrations): generate 0093_medical_sentinel and snapshots after merging staging * reconcile with other merge * fix trigger test * remove extra use effect * fix test url" * don't swallow serializer errors * fix lint * fix tests * fix tests * expose root for generic webhook * root access for webhook * add is workflow ready check correctly
1 parent 2c7c8d5 commit 928581f

File tree

58 files changed

+9683
-1675
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+9683
-1675
lines changed

apps/sim/app/api/__test-utils__/utils.ts

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -147,20 +147,63 @@ export const sampleWorkflowState = {
147147
isDeployed: false,
148148
}
149149

150+
// Global mock data that can be configured by tests
151+
export const globalMockData = {
152+
webhooks: [] as any[],
153+
workflows: [] as any[],
154+
schedules: [] as any[],
155+
shouldThrowError: false,
156+
errorMessage: 'Database error',
157+
}
158+
150159
export const mockDb = {
151-
select: vi.fn().mockImplementation(() => ({
152-
from: vi.fn().mockImplementation(() => ({
153-
where: vi.fn().mockImplementation(() => ({
154-
limit: vi.fn().mockImplementation(() => [
155-
{
156-
id: 'workflow-id',
157-
userId: 'user-id',
158-
state: sampleWorkflowState,
159-
},
160-
]),
160+
select: vi.fn().mockImplementation(() => {
161+
if (globalMockData.shouldThrowError) {
162+
throw new Error(globalMockData.errorMessage)
163+
}
164+
return {
165+
from: vi.fn().mockImplementation(() => ({
166+
innerJoin: vi.fn().mockImplementation(() => ({
167+
where: vi.fn().mockImplementation(() => ({
168+
limit: vi.fn().mockImplementation(() => {
169+
// Return webhook/workflow join data if available
170+
if (globalMockData.webhooks.length > 0) {
171+
return [
172+
{
173+
webhook: globalMockData.webhooks[0],
174+
workflow: globalMockData.workflows[0] || {
175+
id: 'test-workflow',
176+
userId: 'test-user',
177+
},
178+
},
179+
]
180+
}
181+
return []
182+
}),
183+
})),
184+
})),
185+
where: vi.fn().mockImplementation(() => ({
186+
limit: vi.fn().mockImplementation(() => {
187+
// Return schedules if available
188+
if (globalMockData.schedules.length > 0) {
189+
return globalMockData.schedules
190+
}
191+
// Return simple workflow data
192+
if (globalMockData.workflows.length > 0) {
193+
return globalMockData.workflows
194+
}
195+
return [
196+
{
197+
id: 'workflow-id',
198+
userId: 'user-id',
199+
state: sampleWorkflowState,
200+
},
201+
]
202+
}),
203+
})),
161204
})),
162-
})),
163-
})),
205+
}
206+
}),
164207
update: vi.fn().mockImplementation(() => ({
165208
set: vi.fn().mockImplementation(() => ({
166209
where: vi.fn().mockResolvedValue([]),
@@ -351,6 +394,27 @@ export function mockExecutionDependencies() {
351394

352395
vi.mock('@sim/db', () => ({
353396
db: mockDb,
397+
// Add common schema exports that tests might need
398+
webhook: {
399+
id: 'id',
400+
path: 'path',
401+
workflowId: 'workflowId',
402+
isActive: 'isActive',
403+
provider: 'provider',
404+
providerConfig: 'providerConfig',
405+
},
406+
workflow: { id: 'id', userId: 'userId' },
407+
workflowSchedule: {
408+
id: 'id',
409+
workflowId: 'workflowId',
410+
nextRunAt: 'nextRunAt',
411+
status: 'status',
412+
},
413+
userStats: {
414+
userId: 'userId',
415+
totalScheduledExecutions: 'totalScheduledExecutions',
416+
lastActive: 'lastActive',
417+
},
354418
}))
355419
}
356420

apps/sim/app/api/chat/utils.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ describe('Chat API Utils', () => {
6868
})
6969

7070
describe('Auth token utils', () => {
71-
it.concurrent('should encrypt and validate auth tokens', async () => {
71+
it('should encrypt and validate auth tokens', async () => {
7272
const { encryptAuthToken, validateAuthToken } = await import('@/app/api/chat/utils')
7373

7474
const subdomainId = 'test-subdomain-id'
@@ -85,7 +85,7 @@ describe('Chat API Utils', () => {
8585
expect(isInvalidSubdomain).toBe(false)
8686
})
8787

88-
it.concurrent('should reject expired tokens', async () => {
88+
it('should reject expired tokens', async () => {
8989
const { validateAuthToken } = await import('@/app/api/chat/utils')
9090

9191
const subdomainId = 'test-subdomain-id'
@@ -100,7 +100,7 @@ describe('Chat API Utils', () => {
100100
})
101101

102102
describe('Cookie handling', () => {
103-
it.concurrent('should set auth cookie correctly', async () => {
103+
it('should set auth cookie correctly', async () => {
104104
const { setChatAuthCookie } = await import('@/app/api/chat/utils')
105105

106106
const mockSet = vi.fn()
@@ -129,7 +129,7 @@ describe('Chat API Utils', () => {
129129
})
130130

131131
describe('CORS handling', () => {
132-
it.concurrent('should add CORS headers for localhost in development', async () => {
132+
it('should add CORS headers for localhost in development', async () => {
133133
const { addCorsHeaders } = await import('@/app/api/chat/utils')
134134

135135
const mockRequest = {
@@ -164,7 +164,7 @@ describe('Chat API Utils', () => {
164164
)
165165
})
166166

167-
it.concurrent('should handle OPTIONS request', async () => {
167+
it('should handle OPTIONS request', async () => {
168168
const { OPTIONS } = await import('@/app/api/chat/utils')
169169

170170
const mockRequest = {
@@ -198,7 +198,7 @@ describe('Chat API Utils', () => {
198198
})
199199
})
200200

201-
it.concurrent('should allow access to public chats', async () => {
201+
it('should allow access to public chats', async () => {
202202
const utils = await import('@/app/api/chat/utils')
203203
const { validateChatAuth } = utils
204204

@@ -218,7 +218,7 @@ describe('Chat API Utils', () => {
218218
expect(result.authorized).toBe(true)
219219
})
220220

221-
it.concurrent('should request password auth for GET requests', async () => {
221+
it('should request password auth for GET requests', async () => {
222222
const { validateChatAuth } = await import('@/app/api/chat/utils')
223223

224224
const deployment = {
@@ -266,7 +266,7 @@ describe('Chat API Utils', () => {
266266
expect(result.authorized).toBe(true)
267267
})
268268

269-
it.concurrent('should reject incorrect password', async () => {
269+
it('should reject incorrect password', async () => {
270270
const { validateChatAuth } = await import('@/app/api/chat/utils')
271271

272272
const deployment = {
@@ -292,7 +292,7 @@ describe('Chat API Utils', () => {
292292
expect(result.error).toBe('Invalid password')
293293
})
294294

295-
it.concurrent('should request email auth for email-protected chats', async () => {
295+
it('should request email auth for email-protected chats', async () => {
296296
const { validateChatAuth } = await import('@/app/api/chat/utils')
297297

298298
const deployment = {
@@ -314,7 +314,7 @@ describe('Chat API Utils', () => {
314314
expect(result.error).toBe('auth_required_email')
315315
})
316316

317-
it.concurrent('should check allowed emails for email auth', async () => {
317+
it('should check allowed emails for email auth', async () => {
318318
const { validateChatAuth } = await import('@/app/api/chat/utils')
319319

320320
const deployment = {

apps/sim/app/api/chat/utils.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -399,11 +399,10 @@ export async function executeWorkflowForChat(
399399
`[${requestId}] Using ${outputBlockIds.length} output blocks and ${selectedOutputIds.length} selected output IDs for extraction`
400400
)
401401

402-
// Find the workflow (deployedState is NOT deprecated - needed for chat execution)
402+
// Find the workflow to check if it's deployed
403403
const workflowResult = await db
404404
.select({
405405
isDeployed: workflow.isDeployed,
406-
deployedState: workflow.deployedState,
407406
variables: workflow.variables,
408407
})
409408
.from(workflow)
@@ -415,13 +414,17 @@ export async function executeWorkflowForChat(
415414
throw new Error('Workflow not available')
416415
}
417416

418-
// For chat execution, use ONLY the deployed state (no fallback)
419-
if (!workflowResult[0].deployedState) {
417+
// Load the active deployed state from the deployment versions table
418+
const { loadDeployedWorkflowState } = await import('@/lib/workflows/db-helpers')
419+
420+
let deployedState: WorkflowState
421+
try {
422+
deployedState = await loadDeployedWorkflowState(workflowId)
423+
} catch (error) {
424+
logger.error(`[${requestId}] Failed to load deployed state for workflow ${workflowId}:`, error)
420425
throw new Error(`Workflow must be deployed to be available for chat`)
421426
}
422427

423-
// Use deployed state for chat execution (this is the stable, deployed version)
424-
const deployedState = workflowResult[0].deployedState as WorkflowState
425428
const { blocks, edges, loops, parallels } = deployedState
426429

427430
// Prepare for execution, similar to use-workflow-execution.ts
@@ -611,6 +614,7 @@ export async function executeWorkflowForChat(
611614
target: e.target,
612615
})),
613616
onStream,
617+
isDeployedContext: true,
614618
},
615619
})
616620

0 commit comments

Comments
 (0)