From 3db8f824498145c4220be067b6bc15d888b0cbcd Mon Sep 17 00:00:00 2001 From: Waleed Date: Thu, 11 Dec 2025 00:34:08 -0800 Subject: [PATCH] feat(deployment-version): capture deployment version in log (#2304) * feat(deployment-version): capture deployment version in log * improvement: terminal store, logs version, toolbar --------- Co-authored-by: Emir Karabeg --- apps/sim/app/api/logs/[id]/route.ts | 17 +- apps/sim/app/api/logs/route.ts | 21 +- apps/sim/app/api/v1/logs/route.ts | 2 + .../app/api/workflows/[id]/execute/route.ts | 5 + .../[workspaceId]/knowledge/knowledge.tsx | 2 +- .../components/line-chart/line-chart.tsx | 18 +- .../logs/components/dashboard/dashboard.tsx | 15 +- .../components/log-details/log-details.tsx | 22 +- .../components/logs-toolbar/logs-toolbar.tsx | 7 +- .../app/workspace/[workspaceId]/logs/logs.tsx | 2 +- .../components/terminal/terminal.tsx | 5 +- apps/sim/background/schedule-execution.ts | 2 + apps/sim/background/webhook-execution.ts | 59 +- apps/sim/executor/execution/snapshot.ts | 1 + apps/sim/lib/logs/execution/logger.ts | 5 +- .../sim/lib/logs/execution/logging-session.ts | 8 +- .../lib/workflows/executor/execution-core.ts | 4 + apps/sim/lib/workflows/persistence/utils.ts | 10 +- apps/sim/stores/logs/filters/types.ts | 2 + apps/sim/stores/logs/store.ts | 4 +- apps/sim/stores/terminal/store.ts | 17 +- packages/db/migrations/0121_new_mantis.sql | 3 + .../db/migrations/meta/0121_snapshot.json | 7816 +++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + packages/db/schema.ts | 7 + 25 files changed, 8020 insertions(+), 41 deletions(-) create mode 100644 packages/db/migrations/0121_new_mantis.sql create mode 100644 packages/db/migrations/meta/0121_snapshot.json diff --git a/apps/sim/app/api/logs/[id]/route.ts b/apps/sim/app/api/logs/[id]/route.ts index 82e8deb09..466868c08 100644 --- a/apps/sim/app/api/logs/[id]/route.ts +++ b/apps/sim/app/api/logs/[id]/route.ts @@ -1,5 +1,10 @@ import { db } from '@sim/db' -import { permissions, workflow, workflowExecutionLogs } from '@sim/db/schema' +import { + permissions, + workflow, + workflowDeploymentVersion, + workflowExecutionLogs, +} from '@sim/db/schema' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' @@ -29,6 +34,7 @@ export async function GET(_request: NextRequest, { params }: { params: Promise<{ workflowId: workflowExecutionLogs.workflowId, executionId: workflowExecutionLogs.executionId, stateSnapshotId: workflowExecutionLogs.stateSnapshotId, + deploymentVersionId: workflowExecutionLogs.deploymentVersionId, level: workflowExecutionLogs.level, trigger: workflowExecutionLogs.trigger, startedAt: workflowExecutionLogs.startedAt, @@ -46,9 +52,15 @@ export async function GET(_request: NextRequest, { params }: { params: Promise<{ workflowWorkspaceId: workflow.workspaceId, workflowCreatedAt: workflow.createdAt, workflowUpdatedAt: workflow.updatedAt, + deploymentVersion: workflowDeploymentVersion.version, + deploymentVersionName: workflowDeploymentVersion.name, }) .from(workflowExecutionLogs) .innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id)) + .leftJoin( + workflowDeploymentVersion, + eq(workflowDeploymentVersion.id, workflowExecutionLogs.deploymentVersionId) + ) .innerJoin( permissions, and( @@ -81,6 +93,9 @@ export async function GET(_request: NextRequest, { params }: { params: Promise<{ id: log.id, workflowId: log.workflowId, executionId: log.executionId, + deploymentVersionId: log.deploymentVersionId, + deploymentVersion: log.deploymentVersion ?? null, + deploymentVersionName: log.deploymentVersionName ?? null, level: log.level, duration: log.totalDurationMs ? `${log.totalDurationMs}ms` : null, trigger: log.trigger, diff --git a/apps/sim/app/api/logs/route.ts b/apps/sim/app/api/logs/route.ts index 0020e1e6c..9651e9a28 100644 --- a/apps/sim/app/api/logs/route.ts +++ b/apps/sim/app/api/logs/route.ts @@ -1,5 +1,11 @@ import { db } from '@sim/db' -import { pausedExecutions, permissions, workflow, workflowExecutionLogs } from '@sim/db/schema' +import { + pausedExecutions, + permissions, + workflow, + workflowDeploymentVersion, + workflowExecutionLogs, +} from '@sim/db/schema' import { and, desc, eq, gte, inArray, isNotNull, isNull, lte, or, type SQL, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -51,6 +57,7 @@ export async function GET(request: NextRequest) { workflowId: workflowExecutionLogs.workflowId, executionId: workflowExecutionLogs.executionId, stateSnapshotId: workflowExecutionLogs.stateSnapshotId, + deploymentVersionId: workflowExecutionLogs.deploymentVersionId, level: workflowExecutionLogs.level, trigger: workflowExecutionLogs.trigger, startedAt: workflowExecutionLogs.startedAt, @@ -71,6 +78,8 @@ export async function GET(request: NextRequest) { pausedStatus: pausedExecutions.status, pausedTotalPauseCount: pausedExecutions.totalPauseCount, pausedResumedCount: pausedExecutions.resumedCount, + deploymentVersion: workflowDeploymentVersion.version, + deploymentVersionName: workflowDeploymentVersion.name, } : { // Basic mode - exclude large fields for better performance @@ -78,6 +87,7 @@ export async function GET(request: NextRequest) { workflowId: workflowExecutionLogs.workflowId, executionId: workflowExecutionLogs.executionId, stateSnapshotId: workflowExecutionLogs.stateSnapshotId, + deploymentVersionId: workflowExecutionLogs.deploymentVersionId, level: workflowExecutionLogs.level, trigger: workflowExecutionLogs.trigger, startedAt: workflowExecutionLogs.startedAt, @@ -98,6 +108,8 @@ export async function GET(request: NextRequest) { pausedStatus: pausedExecutions.status, pausedTotalPauseCount: pausedExecutions.totalPauseCount, pausedResumedCount: pausedExecutions.resumedCount, + deploymentVersion: workflowDeploymentVersion.version, + deploymentVersionName: sql`NULL`, // Only needed in full mode for details panel } const baseQuery = db @@ -107,6 +119,10 @@ export async function GET(request: NextRequest) { pausedExecutions, eq(pausedExecutions.executionId, workflowExecutionLogs.executionId) ) + .leftJoin( + workflowDeploymentVersion, + eq(workflowDeploymentVersion.id, workflowExecutionLogs.deploymentVersionId) + ) .innerJoin( workflow, and( @@ -397,6 +413,9 @@ export async function GET(request: NextRequest) { id: log.id, workflowId: log.workflowId, executionId: log.executionId, + deploymentVersionId: log.deploymentVersionId, + deploymentVersion: log.deploymentVersion ?? null, + deploymentVersionName: log.deploymentVersionName ?? null, level: log.level, duration: log.totalDurationMs ? `${log.totalDurationMs}ms` : null, trigger: log.trigger, diff --git a/apps/sim/app/api/v1/logs/route.ts b/apps/sim/app/api/v1/logs/route.ts index d4ebc972e..dc6352299 100644 --- a/apps/sim/app/api/v1/logs/route.ts +++ b/apps/sim/app/api/v1/logs/route.ts @@ -111,6 +111,7 @@ export async function GET(request: NextRequest) { id: workflowExecutionLogs.id, workflowId: workflowExecutionLogs.workflowId, executionId: workflowExecutionLogs.executionId, + deploymentVersionId: workflowExecutionLogs.deploymentVersionId, level: workflowExecutionLogs.level, trigger: workflowExecutionLogs.trigger, startedAt: workflowExecutionLogs.startedAt, @@ -161,6 +162,7 @@ export async function GET(request: NextRequest) { id: log.id, workflowId: log.workflowId, executionId: log.executionId, + deploymentVersionId: log.deploymentVersionId, level: log.level, trigger: log.trigger, startedAt: log.startedAt.toISOString(), diff --git a/apps/sim/app/api/workflows/[id]/execute/route.ts b/apps/sim/app/api/workflows/[id]/execute/route.ts index 52dce524a..a330dd624 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.ts @@ -430,6 +430,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: edges: any[] loops: Record parallels: Record + deploymentVersionId?: string } | null = null let processedInput = input @@ -444,6 +445,10 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: edges: workflowData.edges, loops: workflowData.loops || {}, parallels: workflowData.parallels || {}, + deploymentVersionId: + !shouldUseDraftState && 'deploymentVersionId' in workflowData + ? (workflowData.deploymentVersionId as string) + : undefined, } const serializedWorkflow = new Serializer().serializeWorkflow( diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx index f9c161485..757b0731f 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx @@ -199,7 +199,7 @@ export function Knowledge() { -
+
{isLoading ? ( ) : filteredAndSortedKnowledgeBases.length === 0 ? ( diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/components/line-chart/line-chart.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/components/line-chart/line-chart.tsx index 5c9053cc3..cde20df02 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/components/line-chart/line-chart.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/components/line-chart/line-chart.tsx @@ -30,8 +30,8 @@ export function LineChart({ }) { const containerRef = useRef(null) const uniqueId = useRef(`chart-${Math.random().toString(36).substring(2, 9)}`).current - const [containerWidth, setContainerWidth] = useState(420) - const width = containerWidth + const [containerWidth, setContainerWidth] = useState(null) + const width = containerWidth ?? 0 const height = 166 const padding = { top: 16, right: 28, bottom: 26, left: 26 } useEffect(() => { @@ -39,14 +39,14 @@ export function LineChart({ const element = containerRef.current const ro = new ResizeObserver((entries) => { const entry = entries[0] - if (entry?.contentRect) { + if (entry?.contentRect && entry.contentRect.width > 0) { const w = Math.max(280, Math.floor(entry.contentRect.width)) setContainerWidth(w) } }) ro.observe(element) const rect = element.getBoundingClientRect() - if (rect?.width) setContainerWidth(Math.max(280, Math.floor(rect.width))) + if (rect?.width && rect.width > 0) setContainerWidth(Math.max(280, Math.floor(rect.width))) return () => ro.disconnect() }, []) const chartWidth = width - padding.left - padding.right @@ -95,6 +95,16 @@ export function LineChart({ const hasExternalWrapper = !label || label === '' + if (containerWidth === null) { + return ( +
+ ) + } + if (data.length === 0) { return (
{/* Skeleton bars simulating chart */}
- {Array.from({ length: 24 }).map((_, i) => ( + {SKELETON_BAR_HEIGHTS.map((height, i) => ( ))} @@ -803,7 +811,6 @@ export default function Dashboard({
{globalDetails ? ( {globalDetails ? ( {globalDetails ? ( +
{/* Level */}
@@ -233,14 +233,30 @@ export function LogDetails({
{/* Duration */} -
+
Duration - + {log.duration || '—'}
+ + {/* Version */} + {log.deploymentVersion && ( +
+ + Version + +
+ + {log.deploymentVersionName || `v${log.deploymentVersion}`} + +
+
+ )}
{/* Workflow State */} diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/logs-toolbar.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/logs-toolbar.tsx index e29715647..692849fa2 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/logs-toolbar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/logs-toolbar.tsx @@ -343,17 +343,18 @@ export function LogsToolbar({ {/* View mode toggle */} -
+
onViewModeChange(isDashboardView ? 'logs' : 'dashboard')} + >