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 <emirkarabeg@berkeley.edu>
This commit is contained in:
Waleed
2025-12-11 00:34:08 -08:00
committed by GitHub
parent 207a14970b
commit 3db8f82449
25 changed files with 8020 additions and 41 deletions

View File

@@ -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,

View File

@@ -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>`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,

View File

@@ -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(),

View File

@@ -430,6 +430,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
edges: any[]
loops: Record<string, any>
parallels: Record<string, any>
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(

View File

@@ -199,7 +199,7 @@ export function Knowledge() {
</div>
</div>
<div className='mt-[24px] grid grid-cols-1 gap-x-[20px] gap-y-[40px] md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4'>
<div className='mt-[24px] grid grid-cols-1 gap-[20px] md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4'>
{isLoading ? (
<BaseCardSkeletonGrid count={8} />
) : filteredAndSortedKnowledgeBases.length === 0 ? (

View File

@@ -30,8 +30,8 @@ export function LineChart({
}) {
const containerRef = useRef<HTMLDivElement | null>(null)
const uniqueId = useRef(`chart-${Math.random().toString(36).substring(2, 9)}`).current
const [containerWidth, setContainerWidth] = useState<number>(420)
const width = containerWidth
const [containerWidth, setContainerWidth] = useState<number | null>(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 (
<div
ref={containerRef}
className={cn('w-full', !hasExternalWrapper && 'rounded-lg border bg-card p-4')}
style={{ height }}
/>
)
}
if (data.length === 0) {
return (
<div

View File

@@ -40,6 +40,14 @@ interface WorkflowExecution {
const DEFAULT_SEGMENTS = 72
const MIN_SEGMENT_PX = 10
/**
* Predetermined heights for skeleton bars to avoid hydration mismatch.
* Using static values instead of Math.random() ensures server/client consistency.
*/
const SKELETON_BAR_HEIGHTS = [
45, 72, 38, 85, 52, 68, 30, 90, 55, 42, 78, 35, 88, 48, 65, 28, 82, 58, 40, 75, 32, 95, 50, 70,
]
/**
* Skeleton loader for a single graph card
*/
@@ -56,12 +64,12 @@ function GraphCardSkeleton({ title }: { title: string }) {
<div className='flex h-[166px] flex-col justify-end gap-[4px]'>
{/* Skeleton bars simulating chart */}
<div className='flex items-end gap-[2px]'>
{Array.from({ length: 24 }).map((_, i) => (
{SKELETON_BAR_HEIGHTS.map((height, i) => (
<Skeleton
key={i}
className='flex-1'
style={{
height: `${Math.random() * 80 + 20}%`,
height: `${height}%`,
}}
/>
))}
@@ -803,7 +811,6 @@ export default function Dashboard({
<div className='flex-1 overflow-y-auto rounded-t-[6px] bg-[var(--surface-1)] px-[14px] py-[10px]'>
{globalDetails ? (
<LineChart
key={`runs-${expandedWorkflowId || 'all'}-${Object.keys(selectedSegments).length}-${filteredExecutions.length}`}
data={globalDetails.executionCounts}
label=''
color='var(--brand-tertiary)'
@@ -832,7 +839,6 @@ export default function Dashboard({
<div className='flex-1 overflow-y-auto rounded-t-[6px] bg-[var(--surface-1)] px-[14px] py-[10px]'>
{globalDetails ? (
<LineChart
key={`errors-${expandedWorkflowId || 'all'}-${Object.keys(selectedSegments).length}-${filteredExecutions.length}`}
data={globalDetails.failureCounts}
label=''
color='var(--text-error)'
@@ -861,7 +867,6 @@ export default function Dashboard({
<div className='flex-1 overflow-y-auto rounded-t-[6px] bg-[var(--surface-1)] px-[14px] py-[10px]'>
{globalDetails ? (
<LineChart
key={`latency-${expandedWorkflowId || 'all'}-${Object.keys(selectedSegments).length}-${filteredExecutions.length}`}
data={globalDetails.latencies}
label=''
color='var(--c-F59E0B)'

View File

@@ -209,7 +209,7 @@ export function LogDetails({
)}
{/* Details Section */}
<div className='flex flex-col'>
<div className='flex min-w-0 flex-col overflow-hidden'>
{/* Level */}
<div className='flex h-[48px] items-center justify-between border-[var(--border)] border-b p-[8px]'>
<span className='font-medium text-[12px] text-[var(--text-tertiary)]'>
@@ -233,14 +233,30 @@ export function LogDetails({
</div>
{/* Duration */}
<div className='flex h-[48px] items-center justify-between p-[8px]'>
<div
className={`flex h-[48px] items-center justify-between border-b p-[8px] ${log.deploymentVersion ? 'border-[var(--border)]' : 'border-transparent'}`}
>
<span className='font-medium text-[12px] text-[var(--text-tertiary)]'>
Duration
</span>
<span className='font-medium text-[14px] text-[var(--text-secondary)]'>
<span className='font-medium text-[13px] text-[var(--text-secondary)]'>
{log.duration || '—'}
</span>
</div>
{/* Version */}
{log.deploymentVersion && (
<div className='flex h-[48px] items-center gap-[8px] p-[8px]'>
<span className='flex-shrink-0 font-medium text-[12px] text-[var(--text-tertiary)]'>
Version
</span>
<div className='flex w-0 flex-1 justify-end'>
<span className='max-w-full truncate rounded-[6px] bg-[#14291B] px-[9px] py-[2px] font-medium text-[#86EFAC] text-[12px]'>
{log.deploymentVersionName || `v${log.deploymentVersion}`}
</span>
</div>
</div>
)}
</div>
{/* Workflow State */}

View File

@@ -343,17 +343,18 @@ export function LogsToolbar({
</Button>
{/* View mode toggle */}
<div className='flex h-[32px] items-center rounded-[6px] border border-[var(--border)] bg-[var(--surface-elevated)] p-[2px]'>
<div
className='flex h-[32px] cursor-pointer items-center rounded-[6px] border border-[var(--border)] bg-[var(--surface-elevated)] p-[2px]'
onClick={() => onViewModeChange(isDashboardView ? 'logs' : 'dashboard')}
>
<Button
variant={!isDashboardView ? 'active' : 'ghost'}
onClick={() => onViewModeChange('logs')}
className='h-[26px] rounded-[4px] px-[10px]'
>
Logs
</Button>
<Button
variant={isDashboardView ? 'active' : 'ghost'}
onClick={() => onViewModeChange('dashboard')}
className='h-[26px] rounded-[4px] px-[10px]'
>
Dashboard

View File

@@ -609,7 +609,7 @@ export default function Logs() {
{/* Log Details - rendered inside table container */}
<LogDetails
log={logDetailQuery.data || selectedLog}
log={logDetailQuery.data ? { ...selectedLog, ...logDetailQuery.data } : selectedLog}
isOpen={isSidebarOpen}
onClose={handleCloseSidebar}
onNavigateNext={handleNavigateNext}

View File

@@ -298,6 +298,8 @@ export function Terminal() {
setOutputPanelWidth,
openOnRun,
setOpenOnRun,
wrapText,
setWrapText,
setHasHydrated,
} = useTerminalStore()
const isExpanded = useTerminalStore((state) => state.terminalHeight > NEAR_MIN_THRESHOLD)
@@ -312,7 +314,6 @@ export function Terminal() {
const exportConsoleCSV = useTerminalConsoleStore((state) => state.exportConsoleCSV)
const [selectedEntry, setSelectedEntry] = useState<ConsoleEntry | null>(null)
const [isToggling, setIsToggling] = useState(false)
const [wrapText, setWrapText] = useState(true)
const [showCopySuccess, setShowCopySuccess] = useState(false)
const [showInput, setShowInput] = useState(false)
const [autoSelectEnabled, setAutoSelectEnabled] = useState(true)
@@ -1528,7 +1529,7 @@ export function Terminal() {
showCheck
onClick={(e) => {
e.stopPropagation()
setWrapText((prev) => !prev)
setWrapText(!wrapText)
}}
>
<span>Wrap text</span>

View File

@@ -193,6 +193,7 @@ async function runWorkflowExecution({
const deployedData = await loadDeployedWorkflowState(payload.workflowId)
const blocks = deployedData.blocks
const { deploymentVersionId } = deployedData
logger.info(`[${requestId}] Loaded deployed workflow ${payload.workflowId}`)
if (payload.blockId) {
@@ -233,6 +234,7 @@ async function runWorkflowExecution({
userId: actorUserId,
workspaceId: workflowRecord.workspaceId || '',
variables: variables || {},
deploymentVersionId,
})
const metadata: ExecutionMetadata = {

View File

@@ -138,18 +138,26 @@ async function executeWebhookJobInternal(
requestId
)
// Track deploymentVersionId at function scope so it's available in catch block
let deploymentVersionId: string | undefined
try {
const workflowData =
payload.executionTarget === 'live'
const useDraftState = payload.executionTarget === 'live'
const workflowData = useDraftState
? await loadWorkflowFromNormalizedTables(payload.workflowId)
: await loadDeployedWorkflowState(payload.workflowId)
if (!workflowData) {
throw new Error(
`Workflow state not found. The workflow may not be ${payload.executionTarget === 'live' ? 'saved' : 'deployed'} or the deployment data may be corrupted.`
`Workflow state not found. The workflow may not be ${useDraftState ? 'saved' : 'deployed'} or the deployment data may be corrupted.`
)
}
const { blocks, edges, loops, parallels } = workflowData
// Only deployed executions have a deployment version ID
deploymentVersionId =
!useDraftState && 'deploymentVersionId' in workflowData
? (workflowData.deploymentVersionId as string)
: undefined
const wfRows = await db
.select({ workspaceId: workflowTable.workspaceId, variables: workflowTable.variables })
@@ -229,6 +237,13 @@ async function executeWebhookJobInternal(
useDraftState: false,
startTime: new Date().toISOString(),
isClientSession: false,
workflowStateOverride: {
blocks,
edges,
loops: loops || {},
parallels: parallels || {},
deploymentVersionId,
},
}
const snapshot = new ExecutionSnapshot(
@@ -280,6 +295,18 @@ async function executeWebhookJobInternal(
// No changes to process
logger.info(`[${requestId}] No Airtable changes to process`)
// Start logging session so the complete call has a log entry to update
await loggingSession.safeStart({
userId: payload.userId,
workspaceId: workspaceId || '',
variables: {},
triggerData: {
isTest: payload.testMode === true,
executionTarget: payload.executionTarget || 'deployed',
},
deploymentVersionId,
})
await loggingSession.safeComplete({
endedAt: new Date().toISOString(),
totalDurationMs: 0,
@@ -325,6 +352,19 @@ async function executeWebhookJobInternal(
if (!input && payload.provider === 'whatsapp') {
logger.info(`[${requestId}] No messages in WhatsApp payload, skipping execution`)
// Start logging session so the complete call has a log entry to update
await loggingSession.safeStart({
userId: payload.userId,
workspaceId: workspaceId || '',
variables: {},
triggerData: {
isTest: payload.testMode === true,
executionTarget: payload.executionTarget || 'deployed',
},
deploymentVersionId,
})
await loggingSession.safeComplete({
endedAt: new Date().toISOString(),
totalDurationMs: 0,
@@ -444,6 +484,13 @@ async function executeWebhookJobInternal(
useDraftState: false,
startTime: new Date().toISOString(),
isClientSession: false,
workflowStateOverride: {
blocks,
edges,
loops: loops || {},
parallels: parallels || {},
deploymentVersionId,
},
}
const snapshot = new ExecutionSnapshot(metadata, workflow, input || {}, workflowVariables, [])
@@ -495,7 +542,6 @@ async function executeWebhookJobInternal(
})
try {
// Ensure logging session is started (safe to call multiple times)
await loggingSession.safeStart({
userId: payload.userId,
workspaceId: '', // May not be available for early errors
@@ -504,6 +550,7 @@ async function executeWebhookJobInternal(
isTest: payload.testMode === true,
executionTarget: payload.executionTarget || 'deployed',
},
deploymentVersionId, // Pass if available (undefined for early errors)
})
const executionResult = (error?.executionResult as ExecutionResult | undefined) || {

View File

@@ -21,6 +21,7 @@ export interface ExecutionMetadata {
edges: Edge[]
loops?: Record<string, any>
parallels?: Record<string, any>
deploymentVersionId?: string // ID of deployment version if this is deployed state
}
}

View File

@@ -113,11 +113,13 @@ export class ExecutionLogger implements IExecutionLoggerService {
trigger: ExecutionTrigger
environment: ExecutionEnvironment
workflowState: WorkflowState
deploymentVersionId?: string
}): Promise<{
workflowLog: WorkflowExecutionLog
snapshot: WorkflowExecutionSnapshot
}> {
const { workflowId, executionId, trigger, environment, workflowState } = params
const { workflowId, executionId, trigger, environment, workflowState, deploymentVersionId } =
params
logger.debug(`Starting workflow execution ${executionId} for workflow ${workflowId}`)
@@ -168,6 +170,7 @@ export class ExecutionLogger implements IExecutionLoggerService {
workflowId,
executionId,
stateSnapshotId: snapshotResult.snapshot.id,
deploymentVersionId: deploymentVersionId ?? null,
level: 'info',
trigger: trigger.type,
startedAt: startTime,

View File

@@ -22,6 +22,7 @@ export interface SessionStartParams {
variables?: Record<string, string>
triggerData?: Record<string, unknown>
skipLogCreation?: boolean // For resume executions - reuse existing log entry
deploymentVersionId?: string // ID of the deployment version used (null for manual/editor executions)
}
export interface SessionCompleteParams {
@@ -65,7 +66,8 @@ export class LoggingSession {
}
async start(params: SessionStartParams = {}): Promise<void> {
const { userId, workspaceId, variables, triggerData, skipLogCreation } = params
const { userId, workspaceId, variables, triggerData, skipLogCreation, deploymentVersionId } =
params
try {
this.trigger = createTriggerObject(this.triggerType, triggerData)
@@ -86,6 +88,7 @@ export class LoggingSession {
trigger: this.trigger,
environment: this.environment,
workflowState: this.workflowState,
deploymentVersionId,
})
if (this.requestId) {
@@ -267,7 +270,7 @@ export class LoggingSession {
// Fallback: create a minimal logging session without full workflow state
try {
const { userId, workspaceId, variables, triggerData } = params
const { userId, workspaceId, variables, triggerData, deploymentVersionId } = params
this.trigger = createTriggerObject(this.triggerType, triggerData)
this.environment = createEnvironmentObject(
this.workflowId,
@@ -290,6 +293,7 @@ export class LoggingSession {
trigger: this.trigger,
environment: this.environment,
workflowState: this.workflowState,
deploymentVersionId,
})
if (this.requestId) {

View File

@@ -111,6 +111,7 @@ export async function executeWorkflowCore(
let edges: Edge[]
let loops
let parallels
let deploymentVersionId: string | undefined
// Use workflowStateOverride if provided (for diff workflows)
if (metadata.workflowStateOverride) {
@@ -118,6 +119,7 @@ export async function executeWorkflowCore(
edges = metadata.workflowStateOverride.edges
loops = metadata.workflowStateOverride.loops || {}
parallels = metadata.workflowStateOverride.parallels || {}
deploymentVersionId = metadata.workflowStateOverride.deploymentVersionId
logger.info(`[${requestId}] Using workflow state override (diff workflow execution)`, {
blocksCount: Object.keys(blocks).length,
@@ -144,6 +146,7 @@ export async function executeWorkflowCore(
edges = deployedData.edges
loops = deployedData.loops
parallels = deployedData.parallels
deploymentVersionId = deployedData.deploymentVersionId
logger.info(`[${requestId}] Using deployed workflow state (deployed execution)`)
}
@@ -174,6 +177,7 @@ export async function executeWorkflowCore(
workspaceId: providedWorkspaceId,
variables,
skipLogCreation, // Skip if resuming an existing execution
deploymentVersionId, // Only set for deployed executions
})
// Process block states with env var substitution using pre-decrypted values

View File

@@ -40,6 +40,10 @@ export interface NormalizedWorkflowData {
isFromNormalizedTables: boolean // Flag to indicate source (true = normalized tables, false = deployed state)
}
export interface DeployedWorkflowData extends NormalizedWorkflowData {
deploymentVersionId: string
}
export async function blockExistsInDeployment(
workflowId: string,
blockId: string
@@ -68,12 +72,11 @@ export async function blockExistsInDeployment(
}
}
export async function loadDeployedWorkflowState(
workflowId: string
): Promise<NormalizedWorkflowData> {
export async function loadDeployedWorkflowState(workflowId: string): Promise<DeployedWorkflowData> {
try {
const [active] = await db
.select({
id: workflowDeploymentVersion.id,
state: workflowDeploymentVersion.state,
createdAt: workflowDeploymentVersion.createdAt,
})
@@ -99,6 +102,7 @@ export async function loadDeployedWorkflowState(
loops: state.loops || {},
parallels: state.parallels || {},
isFromNormalizedTables: false,
deploymentVersionId: active.id,
}
} catch (error) {
logger.error(`Error loading deployed workflow state ${workflowId}:`, error)

View File

@@ -100,6 +100,8 @@ export interface WorkflowLog {
id: string
workflowId: string
executionId?: string | null
deploymentVersion?: number | null
deploymentVersionName?: string | null
level: string
duration: string | null
trigger: string | null

View File

@@ -4,8 +4,8 @@ import { persist } from 'zustand/middleware'
/**
* Width constraints for the log details panel.
*/
export const MIN_LOG_DETAILS_WIDTH = 340
export const DEFAULT_LOG_DETAILS_WIDTH = 340
export const MIN_LOG_DETAILS_WIDTH = 400
export const DEFAULT_LOG_DETAILS_WIDTH = 400
/**
* Returns the maximum log details panel width (50vw).

View File

@@ -20,6 +20,8 @@ interface TerminalState {
setOutputPanelWidth: (width: number) => void
openOnRun: boolean
setOpenOnRun: (open: boolean) => void
wrapText: boolean
setWrapText: (wrap: boolean) => void
/**
* Indicates whether the terminal is currently being resized via mouse drag.
*
@@ -35,8 +37,6 @@ interface TerminalState {
* @param isResizing - True while the terminal is being resized.
*/
setIsResizing: (isResizing: boolean) => void
// displayMode: DisplayMode
// setDisplayMode: (mode: DisplayMode) => void
_hasHydrated: boolean
setHasHydrated: (hasHydrated: boolean) => void
}
@@ -119,10 +119,15 @@ export const useTerminalStore = create<TerminalState>()(
setOpenOnRun: (open) => {
set({ openOnRun: open })
},
// displayMode: DEFAULT_DISPLAY_MODE,
// setDisplayMode: (mode) => {
// set({ displayMode: mode })
// },
wrapText: true,
/**
* Enables or disables text wrapping in the output panel.
*
* @param wrap - Whether output text should wrap.
*/
setWrapText: (wrap) => {
set({ wrapText: wrap })
},
/**
* Indicates whether the terminal store has finished client-side hydration.
*/

View File

@@ -0,0 +1,3 @@
ALTER TABLE "workflow_execution_logs" ADD COLUMN "deployment_version_id" text;--> statement-breakpoint
ALTER TABLE "workflow_execution_logs" ADD CONSTRAINT "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk" FOREIGN KEY ("deployment_version_id") REFERENCES "public"."workflow_deployment_version"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "workflow_execution_logs_deployment_version_id_idx" ON "workflow_execution_logs" USING btree ("deployment_version_id");

File diff suppressed because it is too large Load Diff

View File

@@ -841,6 +841,13 @@
"when": 1765339999291,
"tag": "0120_illegal_moon_knight",
"breakpoints": true
},
{
"idx": 121,
"version": "7",
"when": 1765434895472,
"tag": "0121_new_mantis",
"breakpoints": true
}
]
}

View File

@@ -293,6 +293,10 @@ export const workflowExecutionLogs = pgTable(
stateSnapshotId: text('state_snapshot_id')
.notNull()
.references(() => workflowExecutionSnapshots.id),
deploymentVersionId: text('deployment_version_id').references(
() => workflowDeploymentVersion.id,
{ onDelete: 'set null' }
),
level: text('level').notNull(), // 'info', 'error'
trigger: text('trigger').notNull(), // 'api', 'webhook', 'schedule', 'manual', 'chat'
@@ -311,6 +315,9 @@ export const workflowExecutionLogs = pgTable(
stateSnapshotIdIdx: index('workflow_execution_logs_state_snapshot_id_idx').on(
table.stateSnapshotId
),
deploymentVersionIdIdx: index('workflow_execution_logs_deployment_version_id_idx').on(
table.deploymentVersionId
),
triggerIdx: index('workflow_execution_logs_trigger_idx').on(table.trigger),
levelIdx: index('workflow_execution_logs_level_idx').on(table.level),
startedAtIdx: index('workflow_execution_logs_started_at_idx').on(table.startedAt),