diff --git a/apps/sim/app/api/workflows/[id]/route.ts b/apps/sim/app/api/workflows/[id]/route.ts index 0172f6d68..802e4e447 100644 --- a/apps/sim/app/api/workflows/[id]/route.ts +++ b/apps/sim/app/api/workflows/[id]/route.ts @@ -133,9 +133,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ const finalWorkflowData = { ...workflowData, state: { - // Default values for expected properties deploymentStatuses: {}, - // Data from normalized tables blocks: normalizedData.blocks, edges: normalizedData.edges, loops: normalizedData.loops, @@ -143,8 +141,11 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ lastSaved: Date.now(), isDeployed: workflowData.isDeployed || false, deployedAt: workflowData.deployedAt, + metadata: { + name: workflowData.name, + description: workflowData.description, + }, }, - // Include workflow variables variables: workflowData.variables || {}, } @@ -166,6 +167,10 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ lastSaved: Date.now(), isDeployed: workflowData.isDeployed || false, deployedAt: workflowData.deployedAt, + metadata: { + name: workflowData.name, + description: workflowData.description, + }, }, variables: workflowData.variables || {}, } diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx index 8e4839ef9..d5e5aba12 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx @@ -4,11 +4,14 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { ArrowDown, ArrowUp, + Check, ChevronDown as ChevronDownIcon, ChevronUp, + Clipboard, ExternalLink, Maximize2, RepeatIcon, + Search, SplitIcon, X, } from 'lucide-react' @@ -813,6 +816,13 @@ function PreviewEditorContent({ } = useContextMenu() const [contextMenuData, setContextMenuData] = useState({ content: '', copyOnly: false }) + const [copiedSection, setCopiedSection] = useState<'input' | 'output' | null>(null) + + const handleCopySection = useCallback((content: string, section: 'input' | 'output') => { + navigator.clipboard.writeText(content) + setCopiedSection(section) + setTimeout(() => setCopiedSection(null), 1500) + }, []) const openContextMenu = useCallback( (e: React.MouseEvent, content: string, copyOnly: boolean) => { @@ -862,9 +872,6 @@ function PreviewEditorContent({ } }, [contextMenuData.content]) - /** - * Handles mouse down event on the resize handle to initiate resizing - */ const handleConnectionsResizeMouseDown = useCallback( (e: React.MouseEvent) => { setIsResizing(true) @@ -874,18 +881,12 @@ function PreviewEditorContent({ [connectionsHeight] ) - /** - * Toggle connections collapsed state - */ const toggleConnectionsCollapsed = useCallback(() => { setConnectionsHeight((prev) => prev <= MIN_CONNECTIONS_HEIGHT ? DEFAULT_CONNECTIONS_HEIGHT : MIN_CONNECTIONS_HEIGHT ) }, []) - /** - * Sets up resize event listeners during resize operations - */ useEffect(() => { if (!isResizing) return @@ -1205,7 +1206,11 @@ function PreviewEditorContent({ } emptyMessage='No input data' > -
+
+ {/* Action buttons overlay */} + {!isSearchActive && ( +
+ + + + + + {copiedSection === 'input' ? 'Copied' : 'Copy'} + + + + + + + Search + +
+ )}
)} @@ -1231,7 +1279,7 @@ function PreviewEditorContent({ emptyMessage='No output data' isError={executionData.status === 'error'} > -
+
+ {/* Action buttons overlay */} + {!isSearchActive && ( +
+ + + + + + {copiedSection === 'output' ? 'Copied' : 'Copy'} + + + + + + + Search + +
+ )}
)} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/preview/preview.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/preview/preview.tsx index da6bafe03..47df78e53 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/preview/preview.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/preview/preview.tsx @@ -35,6 +35,7 @@ interface WorkflowStackEntry { workflowState: WorkflowState traceSpans: TraceSpan[] blockExecutions: Record + workflowName: string } /** @@ -144,7 +145,6 @@ export function Preview({ initialSelectedBlockId, autoSelectLeftmost = true, }: PreviewProps) { - /** Initialize pinnedBlockId synchronously to ensure sidebar is present from first render */ const [pinnedBlockId, setPinnedBlockId] = useState(() => { if (initialSelectedBlockId) return initialSelectedBlockId if (autoSelectLeftmost) { @@ -153,17 +153,14 @@ export function Preview({ return null }) - /** Stack for nested workflow navigation. Empty means we're at the root level. */ const [workflowStack, setWorkflowStack] = useState([]) - /** Block executions for the root level */ const rootBlockExecutions = useMemo(() => { if (providedBlockExecutions) return providedBlockExecutions if (!rootTraceSpans || !Array.isArray(rootTraceSpans)) return {} return buildBlockExecutions(rootTraceSpans) }, [providedBlockExecutions, rootTraceSpans]) - /** Current block executions - either from stack or root */ const blockExecutions = useMemo(() => { if (workflowStack.length > 0) { return workflowStack[workflowStack.length - 1].blockExecutions @@ -171,7 +168,6 @@ export function Preview({ return rootBlockExecutions }, [workflowStack, rootBlockExecutions]) - /** Current workflow state - either from stack or root */ const workflowState = useMemo(() => { if (workflowStack.length > 0) { return workflowStack[workflowStack.length - 1].workflowState @@ -179,41 +175,39 @@ export function Preview({ return rootWorkflowState }, [workflowStack, rootWorkflowState]) - /** Whether we're in execution mode (have trace spans/block executions) */ const isExecutionMode = useMemo(() => { return Object.keys(blockExecutions).length > 0 }, [blockExecutions]) - /** Handler to drill down into a nested workflow block */ const handleDrillDown = useCallback( (blockId: string, childWorkflowState: WorkflowState) => { const blockExecution = blockExecutions[blockId] const childTraceSpans = extractChildTraceSpans(blockExecution) const childBlockExecutions = buildBlockExecutions(childTraceSpans) + const workflowName = childWorkflowState.metadata?.name || 'Nested Workflow' + setWorkflowStack((prev) => [ ...prev, { workflowState: childWorkflowState, traceSpans: childTraceSpans, blockExecutions: childBlockExecutions, + workflowName, }, ]) - /** Set pinned block synchronously to avoid double fitView from sidebar resize */ const leftmostId = getLeftmostBlockId(childWorkflowState) setPinnedBlockId(leftmostId) }, [blockExecutions] ) - /** Handler to go back up the stack */ const handleGoBack = useCallback(() => { setWorkflowStack((prev) => prev.slice(0, -1)) setPinnedBlockId(null) }, []) - /** Handlers for node interactions - memoized to prevent unnecessary re-renders */ const handleNodeClick = useCallback((blockId: string) => { setPinnedBlockId(blockId) }, []) @@ -232,6 +226,8 @@ export function Preview({ const isNested = workflowStack.length > 0 + const currentWorkflowName = isNested ? workflowStack[workflowStack.length - 1].workflowName : null + return (
{isNested && ( -
+
Go back to parent workflow + {currentWorkflowName && ( +
+ + {currentWorkflowName} + +
+ )}
)}