From fc230bcc59dccde57fbdc0c3580038e5f27c89fc Mon Sep 17 00:00:00 2001 From: hjlarry Date: Fri, 12 Sep 2025 16:27:12 +0800 Subject: [PATCH] add force update workflow to support restore --- api/controllers/console/app/workflow.py | 3 +++ api/services/workflow_service.py | 4 +++- .../hooks/use-nodes-sync-draft.ts | 19 +++++++++++++++---- .../workflow/header/header-in-restoring.tsx | 2 +- .../components/workflow/hooks-store/store.ts | 3 ++- .../workflow/hooks/use-nodes-sync-draft.ts | 3 ++- 6 files changed, 26 insertions(+), 8 deletions(-) diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index f67c629d48..183351a830 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -107,6 +107,7 @@ class DraftWorkflowApi(Resource): parser.add_argument("hash", type=str, required=False, location="json") parser.add_argument("environment_variables", type=list, required=True, location="json") parser.add_argument("conversation_variables", type=list, required=False, location="json") + parser.add_argument("force_upload", type=bool, required=False, default=False, location="json") args = parser.parse_args() elif "text/plain" in content_type: try: @@ -123,6 +124,7 @@ class DraftWorkflowApi(Resource): "hash": data.get("hash"), "environment_variables": data.get("environment_variables"), "conversation_variables": data.get("conversation_variables"), + "force_upload": data.get("force_upload", False), } except json.JSONDecodeError: return {"message": "Invalid JSON data"}, 400 @@ -151,6 +153,7 @@ class DraftWorkflowApi(Resource): account=current_user, environment_variables=environment_variables, conversation_variables=conversation_variables, + force_upload=args.get("force_upload", False), ) except WorkflowHashNotEqualError: raise DraftWorkflowNotSync() diff --git a/api/services/workflow_service.py b/api/services/workflow_service.py index 5a842a6612..cc637b5037 100644 --- a/api/services/workflow_service.py +++ b/api/services/workflow_service.py @@ -199,15 +199,17 @@ class WorkflowService: account: Account, environment_variables: Sequence[Variable], conversation_variables: Sequence[Variable], + force_upload: bool = False, ) -> Workflow: """ Sync draft workflow + :param force_upload: Skip hash validation when True (for restore operations) :raises WorkflowHashNotEqualError """ # fetch draft workflow by app_model workflow = self.get_draft_workflow(app_model=app_model) - if workflow and workflow.unique_hash != unique_hash: + if workflow and workflow.unique_hash != unique_hash and not force_upload: raise WorkflowHashNotEqualError() # validate features structure diff --git a/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts index 09b0cf2e19..b3b205940f 100644 --- a/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts +++ b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts @@ -122,6 +122,7 @@ export const useNodesSyncDraft = () => { onError?: () => void onSettled?: () => void }, + forceUpload?: boolean, ) => { if (getNodesReadOnly()) return @@ -129,15 +130,15 @@ export const useNodesSyncDraft = () => { // Check leader status at sync time const currentIsLeader = collaborationManager.getIsLeader() - // If not leader, request the leader to sync - if (!currentIsLeader) { + // If not leader and not forcing upload, request the leader to sync + if (!currentIsLeader && !forceUpload) { console.log('Not leader, requesting leader to sync workflow draft') collaborationManager.emitSyncRequest() callback?.onSettled?.() return } - console.log('Leader performing workflow draft sync') + console.log(forceUpload ? 'Force uploading workflow draft' : 'Leader performing workflow draft sync') const postParams = getPostParams() if (postParams) { @@ -145,8 +146,18 @@ export const useNodesSyncDraft = () => { setSyncWorkflowDraftHash, setDraftUpdatedAt, } = workflowStore.getState() + + // Add force_upload parameter if needed + const finalParams = { + ...postParams.params, + ...(forceUpload && { force_upload: true }), + } + try { - const res = await syncWorkflowDraft(postParams) + const res = await syncWorkflowDraft({ + url: postParams.url, + params: finalParams, + }) setSyncWorkflowDraftHash(res.hash) setDraftUpdatedAt(res.updated_at) console.log('Leader successfully synced workflow draft') diff --git a/web/app/components/workflow/header/header-in-restoring.tsx b/web/app/components/workflow/header/header-in-restoring.tsx index f1a4194370..d035d2f189 100644 --- a/web/app/components/workflow/header/header-in-restoring.tsx +++ b/web/app/components/workflow/header/header-in-restoring.tsx @@ -72,7 +72,7 @@ const HeaderInRestoring = ({ onSettled: () => { onRestoreSettled?.() }, - }) + }, true) // Enable forceUpload for restore operation deleteAllInspectVars() invalidAllLastRun() }, [setShowWorkflowVersionHistoryPanel, workflowStore, handleSyncWorkflowDraft, deleteAllInspectVars, invalidAllLastRun, t, onRestoreSettled, appDetail]) diff --git a/web/app/components/workflow/hooks-store/store.ts b/web/app/components/workflow/hooks-store/store.ts index 4e8f74c774..cf0d25ee15 100644 --- a/web/app/components/workflow/hooks-store/store.ts +++ b/web/app/components/workflow/hooks-store/store.ts @@ -21,7 +21,8 @@ type CommonHooksFnMap = { onSuccess?: () => void onError?: () => void onSettled?: () => void - } + }, + forceUpload?: boolean ) => Promise syncWorkflowDraftWhenPageClose: () => void handleRefreshWorkflowDraft: () => void diff --git a/web/app/components/workflow/hooks/use-nodes-sync-draft.ts b/web/app/components/workflow/hooks/use-nodes-sync-draft.ts index e6cc3a97e3..b6d85fdac7 100644 --- a/web/app/components/workflow/hooks/use-nodes-sync-draft.ts +++ b/web/app/components/workflow/hooks/use-nodes-sync-draft.ts @@ -21,12 +21,13 @@ export const useNodesSyncDraft = () => { onError?: () => void onSettled?: () => void }, + forceUpload?: boolean, ) => { if (getNodesReadOnly()) return if (sync) - doSyncWorkflowDraft(notRefreshWhenSyncError, callback) + doSyncWorkflowDraft(notRefreshWhenSyncError, callback, forceUpload) else debouncedSyncWorkflowDraft(doSyncWorkflowDraft) }, [debouncedSyncWorkflowDraft, doSyncWorkflowDraft, getNodesReadOnly])