Skip to content

Commit f3bc1fc

Browse files
icecrasher321waleedlatif1Vikhyath Mondretiadiologydev
authored
v0.2.8: fix + feat + improvement (#621)
* fix(sharing): fixed folders not appearing when sharing workflows (#616) * fix(sharing): fixed folders not appearing when sharing workflows * cleanup * fixed error case * fix(deletions): folder deletions were hanging + use cascade deletions throughout (#620) * use cascade deletion * fix lint --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@vikhyaths-air.lan> * fix(envvars): t3-env standardization (#606) * chore: use t3-env as source of truth * chore: update mock env for failing tests * feat(enhanced logs): integration + log visualizer canvas (#618) * feat(logs): enhanced logging system with cleanup and theme fixes - Implement enhanced logging cleanup with S3 archival and retention policies - Fix error propagation in trace spans for manual executions - Add theme-aware styling for frozen canvas modal - Integrate enhanced logging system across all execution pathways - Add comprehensive trace span processing and iteration navigation - Fix boolean parameter types in enhanced logs API * add warning for old logs * fix lint * added cost for streaming outputs * fix overflow issue * fix lint * fix selection on closing sidebar * tooltips z index increase --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@vikhyaths-air.lan> Co-authored-by: Waleed Latif <walif6@gmail.com> --------- Co-authored-by: Waleed Latif <walif6@gmail.com> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@vikhyaths-air.lan> Co-authored-by: Aditya Tripathi <aditya@climactic.co>
1 parent 78b5ae7 commit f3bc1fc

File tree

73 files changed

+4865
-961
lines changed

Some content is hidden

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

73 files changed

+4865
-961
lines changed

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

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import { type NextRequest, NextResponse } from 'next/server'
33
import { v4 as uuidv4 } from 'uuid'
44
import { env } from '@/lib/env'
55
import { createLogger } from '@/lib/logs/console-logger'
6-
import { persistExecutionLogs } from '@/lib/logs/execution-logger'
6+
import { EnhancedLoggingSession } from '@/lib/logs/enhanced-logging-session'
77
import { buildTraceSpans } from '@/lib/logs/trace-spans'
8+
import { processStreamingBlockLogs } from '@/lib/tokenization'
89
import { decryptSecret } from '@/lib/utils'
910
import { db } from '@/db'
1011
import { chat, environment as envTable, userStats, workflow } from '@/db/schema'
@@ -252,11 +253,14 @@ export async function executeWorkflowForChat(
252253

253254
const deployment = deploymentResult[0]
254255
const workflowId = deployment.workflowId
256+
const executionId = uuidv4()
257+
258+
// Set up enhanced logging for chat execution
259+
const loggingSession = new EnhancedLoggingSession(workflowId, executionId, 'chat', requestId)
255260

256261
// Check for multi-output configuration in customizations
257262
const customizations = (deployment.customizations || {}) as Record<string, any>
258263
let outputBlockIds: string[] = []
259-
let outputPaths: string[] = []
260264

261265
// Extract output configs from the new schema format
262266
if (deployment.outputConfigs && Array.isArray(deployment.outputConfigs)) {
@@ -271,13 +275,11 @@ export async function executeWorkflowForChat(
271275
})
272276

273277
outputBlockIds = deployment.outputConfigs.map((config) => config.blockId)
274-
outputPaths = deployment.outputConfigs.map((config) => config.path || '')
275278
} else {
276279
// Use customizations as fallback
277280
outputBlockIds = Array.isArray(customizations.outputBlockIds)
278281
? customizations.outputBlockIds
279282
: []
280-
outputPaths = Array.isArray(customizations.outputPaths) ? customizations.outputPaths : []
281283
}
282284

283285
// Fall back to customizations if we still have no outputs
@@ -287,7 +289,6 @@ export async function executeWorkflowForChat(
287289
customizations.outputBlockIds.length > 0
288290
) {
289291
outputBlockIds = customizations.outputBlockIds
290-
outputPaths = customizations.outputPaths || new Array(outputBlockIds.length).fill('')
291292
}
292293

293294
logger.debug(`[${requestId}] Using ${outputBlockIds.length} output blocks for extraction`)
@@ -407,6 +408,13 @@ export async function executeWorkflowForChat(
407408
{} as Record<string, Record<string, any>>
408409
)
409410

411+
// Start enhanced logging session
412+
await loggingSession.safeStart({
413+
userId: deployment.userId,
414+
workspaceId: '', // TODO: Get from workflow
415+
variables: workflowVariables,
416+
})
417+
410418
const stream = new ReadableStream({
411419
async start(controller) {
412420
const encoder = new TextEncoder()
@@ -458,16 +466,41 @@ export async function executeWorkflowForChat(
458466
},
459467
})
460468

461-
const result = await executor.execute(workflowId)
469+
// Set up enhanced logging on the executor
470+
loggingSession.setupExecutor(executor)
471+
472+
let result
473+
try {
474+
result = await executor.execute(workflowId)
475+
} catch (error: any) {
476+
logger.error(`[${requestId}] Chat workflow execution failed:`, error)
477+
await loggingSession.safeCompleteWithError({
478+
endedAt: new Date().toISOString(),
479+
totalDurationMs: 0,
480+
error: {
481+
message: error.message || 'Chat workflow execution failed',
482+
stackTrace: error.stack,
483+
},
484+
})
485+
throw error
486+
}
462487

463488
if (result && 'success' in result) {
464-
result.logs?.forEach((log: BlockLog) => {
465-
if (streamedContent.has(log.blockId)) {
466-
if (log.output) {
467-
log.output.content = streamedContent.get(log.blockId)
489+
// Update streamed content and apply tokenization
490+
if (result.logs) {
491+
result.logs.forEach((log: BlockLog) => {
492+
if (streamedContent.has(log.blockId)) {
493+
const content = streamedContent.get(log.blockId)
494+
if (log.output) {
495+
log.output.content = content
496+
}
468497
}
469-
}
470-
})
498+
})
499+
500+
// Process all logs for streaming tokenization
501+
const processedCount = processStreamingBlockLogs(result.logs, streamedContent)
502+
logger.info(`[CHAT-API] Processed ${processedCount} blocks for streaming tokenization`)
503+
}
471504

472505
const { traceSpans, totalDuration } = buildTraceSpans(result)
473506
const enrichedResult = { ...result, traceSpans, totalDuration }
@@ -481,8 +514,7 @@ export async function executeWorkflowForChat(
481514
;(enrichedResult.metadata as any).conversationId = conversationId
482515
}
483516
const executionId = uuidv4()
484-
await persistExecutionLogs(workflowId, executionId, enrichedResult, 'chat')
485-
logger.debug(`Persisted logs for deployed chat: ${executionId}`)
517+
logger.debug(`Generated execution ID for deployed chat: ${executionId}`)
486518

487519
if (result.success) {
488520
try {
@@ -506,6 +538,17 @@ export async function executeWorkflowForChat(
506538
)
507539
}
508540

541+
// Complete enhanced logging session (for both success and failure)
542+
if (result && 'success' in result) {
543+
const { traceSpans } = buildTraceSpans(result)
544+
await loggingSession.safeComplete({
545+
endedAt: new Date().toISOString(),
546+
totalDurationMs: result.metadata?.duration || 0,
547+
finalOutput: result.output,
548+
traceSpans,
549+
})
550+
}
551+
509552
controller.close()
510553
},
511554
})

apps/sim/app/api/folders/[id]/route.test.ts

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ describe('Individual Folder API Route', () => {
4040
}
4141

4242
const { mockAuthenticatedUser, mockUnauthenticated } = mockAuth(TEST_USER)
43+
const mockGetUserEntityPermissions = vi.fn()
4344

4445
function createFolderDbMock(options: FolderDbMockOptions = {}) {
4546
const {
@@ -109,6 +110,12 @@ describe('Individual Folder API Route', () => {
109110
vi.resetModules()
110111
vi.clearAllMocks()
111112
setupCommonApiMocks()
113+
114+
mockGetUserEntityPermissions.mockResolvedValue('admin')
115+
116+
vi.doMock('@/lib/permissions/utils', () => ({
117+
getUserEntityPermissions: mockGetUserEntityPermissions,
118+
}))
112119
})
113120

114121
afterEach(() => {
@@ -181,6 +188,72 @@ describe('Individual Folder API Route', () => {
181188
expect(data).toHaveProperty('error', 'Unauthorized')
182189
})
183190

191+
it('should return 403 when user has only read permissions', async () => {
192+
mockAuthenticatedUser()
193+
mockGetUserEntityPermissions.mockResolvedValue('read') // Read-only permissions
194+
195+
const dbMock = createFolderDbMock()
196+
vi.doMock('@/db', () => dbMock)
197+
198+
const req = createMockRequest('PUT', {
199+
name: 'Updated Folder',
200+
})
201+
const params = Promise.resolve({ id: 'folder-1' })
202+
203+
const { PUT } = await import('./route')
204+
205+
const response = await PUT(req, { params })
206+
207+
expect(response.status).toBe(403)
208+
209+
const data = await response.json()
210+
expect(data).toHaveProperty('error', 'Write access required to update folders')
211+
})
212+
213+
it('should allow folder update for write permissions', async () => {
214+
mockAuthenticatedUser()
215+
mockGetUserEntityPermissions.mockResolvedValue('write') // Write permissions
216+
217+
const dbMock = createFolderDbMock()
218+
vi.doMock('@/db', () => dbMock)
219+
220+
const req = createMockRequest('PUT', {
221+
name: 'Updated Folder',
222+
})
223+
const params = Promise.resolve({ id: 'folder-1' })
224+
225+
const { PUT } = await import('./route')
226+
227+
const response = await PUT(req, { params })
228+
229+
expect(response.status).toBe(200)
230+
231+
const data = await response.json()
232+
expect(data).toHaveProperty('folder')
233+
})
234+
235+
it('should allow folder update for admin permissions', async () => {
236+
mockAuthenticatedUser()
237+
mockGetUserEntityPermissions.mockResolvedValue('admin') // Admin permissions
238+
239+
const dbMock = createFolderDbMock()
240+
vi.doMock('@/db', () => dbMock)
241+
242+
const req = createMockRequest('PUT', {
243+
name: 'Updated Folder',
244+
})
245+
const params = Promise.resolve({ id: 'folder-1' })
246+
247+
const { PUT } = await import('./route')
248+
249+
const response = await PUT(req, { params })
250+
251+
expect(response.status).toBe(200)
252+
253+
const data = await response.json()
254+
expect(data).toHaveProperty('folder')
255+
})
256+
184257
it('should return 400 when trying to set folder as its own parent', async () => {
185258
mockAuthenticatedUser()
186259

@@ -387,6 +460,68 @@ describe('Individual Folder API Route', () => {
387460
expect(data).toHaveProperty('error', 'Unauthorized')
388461
})
389462

463+
it('should return 403 when user has only read permissions for delete', async () => {
464+
mockAuthenticatedUser()
465+
mockGetUserEntityPermissions.mockResolvedValue('read') // Read-only permissions
466+
467+
const dbMock = createFolderDbMock()
468+
vi.doMock('@/db', () => dbMock)
469+
470+
const req = createMockRequest('DELETE')
471+
const params = Promise.resolve({ id: 'folder-1' })
472+
473+
const { DELETE } = await import('./route')
474+
475+
const response = await DELETE(req, { params })
476+
477+
expect(response.status).toBe(403)
478+
479+
const data = await response.json()
480+
expect(data).toHaveProperty('error', 'Admin access required to delete folders')
481+
})
482+
483+
it('should return 403 when user has only write permissions for delete', async () => {
484+
mockAuthenticatedUser()
485+
mockGetUserEntityPermissions.mockResolvedValue('write') // Write permissions (not enough for delete)
486+
487+
const dbMock = createFolderDbMock()
488+
vi.doMock('@/db', () => dbMock)
489+
490+
const req = createMockRequest('DELETE')
491+
const params = Promise.resolve({ id: 'folder-1' })
492+
493+
const { DELETE } = await import('./route')
494+
495+
const response = await DELETE(req, { params })
496+
497+
expect(response.status).toBe(403)
498+
499+
const data = await response.json()
500+
expect(data).toHaveProperty('error', 'Admin access required to delete folders')
501+
})
502+
503+
it('should allow folder deletion for admin permissions', async () => {
504+
mockAuthenticatedUser()
505+
mockGetUserEntityPermissions.mockResolvedValue('admin') // Admin permissions
506+
507+
const dbMock = createFolderDbMock({
508+
folderLookupResult: mockFolder,
509+
})
510+
vi.doMock('@/db', () => dbMock)
511+
512+
const req = createMockRequest('DELETE')
513+
const params = Promise.resolve({ id: 'folder-1' })
514+
515+
const { DELETE } = await import('./route')
516+
517+
const response = await DELETE(req, { params })
518+
519+
expect(response.status).toBe(200)
520+
521+
const data = await response.json()
522+
expect(data).toHaveProperty('success', true)
523+
})
524+
390525
it('should handle database errors during deletion', async () => {
391526
mockAuthenticatedUser()
392527

0 commit comments

Comments
 (0)