mirror of
https://github.com/langgenius/dify.git
synced 2026-11-13 14:51:39 +08:00
fix(workflow): allow presaves of an existing empty draft
This commit is contained in:
1 parent
7a76aaca36
commit
cbeaad8ea7
3 files changed
+26
-8
No files matched your search
@@ -99,6 +99,7 @@ from services.app.access import resolve_app_access_filter
|
||||
from services.app_generate_service import AppGenerateService
|
||||
from services.errors.app import IsDraftWorkflowError, WorkflowHashNotEqualError, WorkflowNotFoundError
|
||||
from services.errors.llm import InvokeRateLimitError
|
||||
from services.errors.workflow_service import EmptyWorkflowConfirmationRequiredError
|
||||
from services.workflow_ref_service import WorkflowRefService
|
||||
from services.workflow_service import DraftWorkflowDeletionError, WorkflowInUseError, WorkflowService
|
||||
from services.workflow_variable_reference_validator import (
|
||||
@@ -639,7 +640,8 @@ class DraftWorkflowApi(Resource):
|
||||
@rbac_permission_required(RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()))
|
||||
def post(self, current_user: Account, app_model: App):
|
||||
"""
|
||||
Sync draft workflow
|
||||
Sync draft workflow. Empty initialization or clearing requires force=true;
|
||||
an already-empty draft may be saved with its current hash without force.
|
||||
"""
|
||||
content_type = request.headers.get("Content-Type", "")
|
||||
|
||||
@@ -656,11 +658,6 @@ class DraftWorkflowApi(Resource):
|
||||
else:
|
||||
abort(415)
|
||||
|
||||
# Empty autosaves can otherwise replace a populated draft, even with a matching hash.
|
||||
# Require explicit confirmation for both regular saves and text/plain unload beacons.
|
||||
if args_model.graph.get("nodes") == [] and args_model.graph.get("edges") == [] and not args_model.force:
|
||||
raise InvalidArgumentError(description="Saving an empty workflow requires force=true.")
|
||||
|
||||
workflow_service = WorkflowService()
|
||||
|
||||
try:
|
||||
@@ -693,10 +690,11 @@ class DraftWorkflowApi(Resource):
|
||||
deleted_environment_variable_ids=deleted_environment_variable_ids,
|
||||
preserve_environment_variables=True,
|
||||
graph_only=args_model.is_collaborative,
|
||||
require_empty_graph_confirmation=not args_model.force,
|
||||
)
|
||||
except WorkflowHashNotEqualError:
|
||||
raise DraftWorkflowNotSync()
|
||||
except VariableError as e:
|
||||
except (VariableError, EmptyWorkflowConfirmationRequiredError) as e:
|
||||
raise InvalidArgumentError(description=str(e))
|
||||
|
||||
return dump_response(
|
||||
|
||||
@@ -8,3 +8,7 @@ class DraftWorkflowDeletionError(ValueError):
|
||||
"""Raised when attempting to delete a draft workflow"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class EmptyWorkflowConfirmationRequiredError(ValueError):
|
||||
"""Raised when an unconfirmed empty save would initialize or clear a draft."""
|
||||
@@ -152,7 +152,11 @@ from services.workflow.workflow_converter import WorkflowConverter
|
||||
from services.workflow_ref_service import WorkflowRef
|
||||
from services.workflow_version_number_service import allocate_version_number
|
||||
|
||||
from .errors.workflow_service import DraftWorkflowDeletionError, WorkflowInUseError
|
||||
from .errors.workflow_service import (
|
||||
DraftWorkflowDeletionError,
|
||||
EmptyWorkflowConfirmationRequiredError,
|
||||
WorkflowInUseError,
|
||||
)
|
||||
from .human_input_delivery_test_service import (
|
||||
DeliveryTestContext,
|
||||
DeliveryTestEmailRecipient,
|
||||
@@ -424,6 +428,7 @@ class WorkflowService:
|
||||
commit: bool = True,
|
||||
sync_agent_bindings: bool = True,
|
||||
graph_only: bool = False,
|
||||
require_empty_graph_confirmation: bool = False,
|
||||
) -> Workflow:
|
||||
"""
|
||||
Sync draft workflow.
|
||||
@@ -436,7 +441,13 @@ class WorkflowService:
|
||||
graph-only saves preserve independently persisted draft fields, while
|
||||
an explicit per-ID environment patch is merged with the graph.
|
||||
|
||||
Public unconfirmed saves require an empty graph to match an already-empty
|
||||
draft. The False default preserves trusted DSL import initialization and
|
||||
Builder repair/rollback clears; public force=true uses the same overload.
|
||||
Neither overload bypasses the existing draft hash check.
|
||||
|
||||
:raises WorkflowHashNotEqualError
|
||||
:raises EmptyWorkflowConfirmationRequiredError
|
||||
"""
|
||||
if environment_variable_upserts is None and deleted_environment_variable_ids:
|
||||
raise ValueError("Deleted environment variable ids require an environment variable patch.")
|
||||
@@ -447,6 +458,11 @@ class WorkflowService:
|
||||
if workflow and workflow.unique_hash != unique_hash:
|
||||
raise WorkflowHashNotEqualError()
|
||||
|
||||
if require_empty_graph_confirmation and graph.get("nodes") == [] and graph.get("edges") == []:
|
||||
previous_graph = workflow.graph_dict if workflow is not None else None
|
||||
if previous_graph is None or previous_graph.get("nodes") != [] or previous_graph.get("edges") != []:
|
||||
raise EmptyWorkflowConfirmationRequiredError("Saving an empty workflow requires force=true.")
|
||||
|
||||
# Collaboration persists features and variables through dedicated endpoints. A graph save
|
||||
# must not overwrite those newer database values with another collaborator's stale cache.
|
||||
if not graph_only or not workflow:
|
||||
|
||||
Reference in new issue
Block a user