From b1c0f6da8bad323f17ba4412ab90100f72d6e175 Mon Sep 17 00:00:00 2001 From: Yufeng He <40085740+he-yufeng@users.noreply.github.com> Date: Wed, 3 Jun 2026 21:52:27 +0800 Subject: [PATCH] fix: strip local file defaults from app DSL exports --- api/services/app_dsl_service.py | 49 ++++++++++++++ .../services/test_app_dsl_service.py | 66 +++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 api/tests/unit_tests/services/test_app_dsl_service.py diff --git a/api/services/app_dsl_service.py b/api/services/app_dsl_service.py index e13ed351803..22bb97e80b1 100644 --- a/api/services/app_dsl_service.py +++ b/api/services/app_dsl_service.py @@ -52,6 +52,9 @@ CHECK_DEPENDENCIES_REDIS_KEY_PREFIX = "app_check_dependencies:" IMPORT_INFO_REDIS_EXPIRY = 10 * 60 # 10 minutes DSL_MAX_SIZE = 10 * 1024 * 1024 # 10MB CURRENT_DSL_VERSION = CURRENT_APP_DSL_VERSION +FILE_VARIABLE_TYPES = {"file", "file-list"} +LOCAL_FILE_ID_KEYS = {"upload_file_id", "uploadedId"} +LOCAL_FILE_TRANSFER_METHODS = {"local_file"} class Import(BaseModel): @@ -560,6 +563,7 @@ class AppDslService: raise ValueError("Missing draft workflow configuration, please check.") workflow_dict = workflow.to_dict(include_secret=include_secret) + cls._strip_tenant_file_defaults_from_workflow_dict(workflow_dict) # TODO: refactor: we need a better way to filter workspace related data from nodes for node in workflow_dict.get("graph", {}).get("nodes", []): node_data = node.get("data", {}) @@ -599,6 +603,51 @@ class AppDslService: ) ] + @classmethod + def _strip_tenant_file_defaults_from_workflow_dict(cls, workflow_dict: dict[str, Any]) -> None: + for node in workflow_dict.get("graph", {}).get("nodes", []): + node_data = node.get("data", {}) + if not isinstance(node_data, dict): + continue + + variables = node_data.get("variables") + if not isinstance(variables, list): + continue + + for variable in variables: + if not isinstance(variable, dict) or variable.get("type") not in FILE_VARIABLE_TYPES: + continue + if "default" not in variable: + continue + + sanitized_default = cls._sanitize_file_variable_default(variable["default"]) + if sanitized_default is None: + variable.pop("default", None) + else: + variable["default"] = sanitized_default + + @classmethod + def _sanitize_file_variable_default(cls, value: Any) -> Any | None: + if isinstance(value, list): + items = [item for item in (cls._sanitize_file_variable_default(item) for item in value) if item is not None] + return items or None + + if not isinstance(value, dict): + return value + + if cls._is_tenant_local_file_default(value): + return None + + sanitized = dict(value) + for key in LOCAL_FILE_ID_KEYS: + sanitized.pop(key, None) + return sanitized + + @staticmethod + def _is_tenant_local_file_default(value: Mapping[str, Any]) -> bool: + transfer_method = value.get("transfer_method") or value.get("transferMethod") + return transfer_method in LOCAL_FILE_TRANSFER_METHODS or any(key in value for key in LOCAL_FILE_ID_KEYS) + @classmethod def _append_model_config_export_data(cls, export_data: dict[str, Any], app_model: App): """ diff --git a/api/tests/unit_tests/services/test_app_dsl_service.py b/api/tests/unit_tests/services/test_app_dsl_service.py new file mode 100644 index 00000000000..3cad8fbf442 --- /dev/null +++ b/api/tests/unit_tests/services/test_app_dsl_service.py @@ -0,0 +1,66 @@ +from services.app_dsl_service import AppDslService + + +def test_strip_tenant_file_defaults_removes_local_file_ids() -> None: + workflow_dict = { + "graph": { + "nodes": [ + { + "data": { + "variables": [ + { + "variable": "contract", + "type": "file", + "default": { + "name": "contract.pdf", + "transfer_method": "local_file", + "type": "document", + "upload_file_id": "source-workspace-file", + "uploadedId": "source-workspace-file", + }, + }, + { + "variable": "attachments", + "type": "file-list", + "default": [ + { + "name": "local.pdf", + "transferMethod": "local_file", + "type": "document", + "uploadedId": "source-workspace-file-2", + }, + { + "name": "remote.pdf", + "transfer_method": "remote_url", + "url": "https://example.com/remote.pdf", + "type": "document", + }, + ], + }, + { + "variable": "notes", + "type": "text-input", + "default": { + "upload_file_id": "not-a-file-variable", + }, + }, + ] + } + } + ] + } + } + + AppDslService._strip_tenant_file_defaults_from_workflow_dict(workflow_dict) + + variables = workflow_dict["graph"]["nodes"][0]["data"]["variables"] + assert "default" not in variables[0] + assert variables[1]["default"] == [ + { + "name": "remote.pdf", + "transfer_method": "remote_url", + "url": "https://example.com/remote.pdf", + "type": "document", + } + ] + assert variables[2]["default"] == {"upload_file_id": "not-a-file-variable"}