From 38dfd00d9d8e32fdd7c3083ef8fa73048fdf5282 Mon Sep 17 00:00:00 2001 From: Alex Angin Date: Wed, 24 Sep 2025 09:37:49 -0400 Subject: [PATCH] Feature: workflow history, buttons moved on top (#3492) --- .../src/routes/workflows/editor/Workspace.tsx | 363 +++++++++++------- .../editor/panels/WorkflowComparisonPanel.tsx | 129 ++++--- .../editor/panels/WorkflowHistoryPanel.tsx | 44 +-- 3 files changed, 309 insertions(+), 227 deletions(-) diff --git a/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx b/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx index 1d26f313..c933839c 100644 --- a/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx @@ -1,5 +1,11 @@ import { AxiosError } from "axios"; -import { useEffect, useRef, useState, MutableRefObject } from "react"; +import { + useCallback, + useEffect, + useRef, + useState, + MutableRefObject, +} from "react"; import { nanoid } from "nanoid"; import { ChevronRightIcon, @@ -300,6 +306,33 @@ function Workspace({ closeWorkflowPanel(); }); + // Centralized function to manage comparison and panel states + const clearComparisonViewAndShowFreshIfActive = useCallback( + (active: boolean) => { + setWorkflowPanelState({ + active, + content: "history", + data: { + showComparison: false, + version1: undefined, + version2: undefined, + }, + }); + }, + [setWorkflowPanelState], + ); + + // Clear comparison view when switching between browser mode and editor mode + useEffect(() => { + if (workflowPanelState.data?.showComparison) { + clearComparisonViewAndShowFreshIfActive(false); + setShowAllCode(false); + } + // We intentionally omit workflowPanelState.data?.showComparison from deps + // to avoid clearing comparison immediately when it's set + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [showBrowser, clearComparisonViewAndShowFreshIfActive]); + useMountEffect(() => { const closePanelsWhenEscapeIsPressed = (e: KeyboardEvent) => { if (e.key === "Escape") { @@ -572,19 +605,6 @@ function Workspace({ } } - // Centralized function to manage comparison and panel states - function clearComparisonViewAndShowFreshIfActive(active: boolean) { - setWorkflowPanelState({ - active, - content: "history", - data: { - showComparison: false, - version1: undefined, - version2: undefined, - }, - }); - } - function toggleHistoryPanel() { // Capture current state before making changes const wasInComparisonMode = workflowPanelState.data?.showComparison; @@ -895,39 +915,26 @@ function Workspace({ /> - {/* infinite canvas and sub panels when not in debug mode */} - {!showBrowser && ( + {/* comparison view (takes precedence over both browser and non-browser modes) */} + {workflowPanelState.data?.showComparison && + workflowPanelState.data?.version1 && + workflowPanelState.data?.version2 ? (
- {/* infinite canvas or comparison view */} - {workflowPanelState.data?.showComparison && - workflowPanelState.data?.version1 && - workflowPanelState.data?.version2 ? ( -
- -
- ) : ( - + - )} +
{/* sub panels */} {workflowPanelState.active && ( @@ -984,46 +991,122 @@ function Workspace({ )} + ) : ( + <> + {/* infinite canvas and sub panels when not in debug mode */} + {!showBrowser && ( +
+ {/* infinite canvas */} + + + {/* sub panels */} + {workflowPanelState.active && ( +
+ {workflowPanelState.content === "cacheKeyValues" && ( + { + setToDeleteCacheKeyValue(cacheKeyValue); + setOpenConfirmCacheKeyValueDeleteDialogue(true); + }} + onPaginate={(page) => { + setPage(page); + }} + onSelect={(cacheKeyValue) => { + setCacheKeyValue(cacheKeyValue); + setCacheKeyValueFilter(""); + closeWorkflowPanel(); + }} + /> + )} + {workflowPanelState.content === "parameters" && ( +
+ +
+ )} + {workflowPanelState.content === "history" && ( +
+ +
+ )} + {workflowPanelState.content === "nodeLibrary" && ( +
+ { + addNode(props); + }} + /> +
+ )} +
+ )} +
+ )} + )} {/* sub panels when in debug mode */} - {showBrowser && workflowPanelState.active && ( -
- {workflowPanelState.content === "cacheKeyValues" && ( - { - setToDeleteCacheKeyValue(cacheKeyValue); - setOpenConfirmCacheKeyValueDeleteDialogue(true); - }} - onPaginate={(page) => { - setPage(page); - }} - onSelect={(cacheKeyValue) => { - setCacheKeyValue(cacheKeyValue); - setCacheKeyValueFilter(""); - closeWorkflowPanel(); - }} - /> - )} - {workflowPanelState.content === "parameters" && ( - - )} -
- )} + {showBrowser && + !workflowPanelState.data?.showComparison && + workflowPanelState.active && ( +
+ {workflowPanelState.content === "cacheKeyValues" && ( + { + setToDeleteCacheKeyValue(cacheKeyValue); + setOpenConfirmCacheKeyValueDeleteDialogue(true); + }} + onPaginate={(page) => { + setPage(page); + }} + onSelect={(cacheKeyValue) => { + setCacheKeyValue(cacheKeyValue); + setCacheKeyValueFilter(""); + closeWorkflowPanel(); + }} + /> + )} + {workflowPanelState.content === "parameters" && ( + + )} +
+ )} {/* code, infinite canvas, browser, and timeline when in debug mode */} - {showBrowser && ( + {showBrowser && !workflowPanelState.data?.showComparison && (
setContainerResizeTrigger((prev) => prev + 1)} > - {/* code and infinite canvas or comparison view */} + {/* code and infinite canvas */}
- {workflowPanelState.data?.showComparison && - workflowPanelState.data?.version1 && - workflowPanelState.data?.version2 ? ( +
+ {/* code */}
- -
- ) : ( -
- {/* code */} -
-
-
- -
- +
+
+
-
- {/* infinite canvas */} -
-
- )} + {/* infinite canvas */} +
+ +
+
{/* browser & timeline */} diff --git a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowComparisonPanel.tsx b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowComparisonPanel.tsx index 2f2bc4ba..10c58f3f 100644 --- a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowComparisonPanel.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowComparisonPanel.tsx @@ -1,5 +1,4 @@ import { useCallback, useMemo } from "react"; -import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; import { @@ -171,7 +170,6 @@ function getWorkflowElements(version: WorkflowVersion) { function WorkflowComparisonRenderer({ version, - onSelectState, blockColors, }: { version: WorkflowVersion; @@ -239,45 +237,19 @@ function WorkflowComparisonRenderer({ ); return ( -
-
-
-
- - {version.title}, version: {version.version} - - - {version.workflow_definition?.blocks?.length || 0} block - {(version.workflow_definition?.blocks?.length || 0) !== 1 - ? "s" - : ""} - - {onSelectState && ( - - )} -
-
-
-
- -
+
+
); } @@ -349,23 +321,70 @@ function WorkflowComparisonPanel({ version1, version2, onSelectState }: Props) {
{/* Header */}
-

Version Comparison

-
-
-
- Identical ({stats.identical}) + {/* 3x3 Grid Layout */} +
+ {/* Row 1: Workflow Names and Title */} +

+ {version1.title} +

+

+ Version Comparison +

+

+ {version2.title} +

+ + {/* Row 2: Version Details and Statistics */} +
+ [Version {version1.version}] •{" "} + {new Date(version1.modified_at).toLocaleDateString()}
-
-
- Modified ({stats.modified}) +
+
+
+ Identical ({stats.identical}) +
+
+
+ Modified ({stats.modified}) +
+
+
+ Added ({stats.added}) +
+
+
+ Removed ({stats.removed}) +
-
-
- Added ({stats.added}) +
+ [Version {version2.version}] •{" "} + {new Date(version2.modified_at).toLocaleDateString()}
-
-
- Removed ({stats.removed}) + + {/* Row 3: Select Buttons */} +
+ {onSelectState && ( + + )} +
+
+
+ {onSelectState && ( + + )}
diff --git a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowHistoryPanel.tsx b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowHistoryPanel.tsx index c23fe213..2be60dba 100644 --- a/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowHistoryPanel.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/panels/WorkflowHistoryPanel.tsx @@ -101,6 +101,27 @@ function WorkflowHistoryPanel({ workflowPermanentId, onCompare }: Props) {

+ {/* Compare Buttons */} +
+
+ + +
+
+ {/* Version List */} @@ -166,29 +187,6 @@ function WorkflowHistoryPanel({ workflowPermanentId, onCompare }: Props) { )}
- - - - {/* Footer */} -
-
- - -
-
); }