reorganize workflow updates so that we can sanely check if we need to prompt user about code cache deletion on the frontend (#3639)

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
This commit is contained in:
Shuchang Zheng
2025-10-07 16:56:53 -07:00
committed by GitHub
parent f48277d298
commit 360def0de5
4 changed files with 328 additions and 241 deletions

View File

@@ -15,6 +15,7 @@ from skyvern.constants import GET_DOWNLOADED_FILES_TIMEOUT, SAVE_DOWNLOADED_FILE
from skyvern.exceptions import (
BlockNotFound,
BrowserSessionNotFound,
CannotUpdateWorkflowDueToCodeCache,
FailedToSendWebhook,
InvalidCredentialId,
MissingValueForParameter,
@@ -115,9 +116,9 @@ DEFAULT_FIRST_BLOCK_LABEL = "block_1"
DEFAULT_WORKFLOW_TITLE = "New Workflow"
def _get_workflow_definition_without_dates(workflow_definition: WorkflowDefinition) -> dict[str, Any]:
def _get_workflow_definition_core_data(workflow_definition: WorkflowDefinition) -> dict[str, Any]:
"""
This function dumps the workflow definition and removes the created_at and modified_at fields inside:
This function dumps the workflow definition and removes the irrelevant data to the definition, like created_at and modified_at fields inside:
- list of blocks
- list of parameters
And return the dumped workflow definition as a python dictionary.
@@ -131,6 +132,13 @@ def _get_workflow_definition_without_dates(workflow_definition: WorkflowDefiniti
"output_parameter_id",
"workflow_id",
"workflow_parameter_id",
"aws_secret_parameter_id",
"bitwarden_login_credential_parameter_id",
"bitwarden_sensitive_information_parameter_id",
"bitwarden_credit_card_data_parameter_id",
"credential_parameter_id",
"onepassword_credential_parameter_id",
"azure_vault_credential_parameter_id",
]
# Use BFS to recursively remove fields from all nested objects
@@ -926,19 +934,14 @@ class WorkflowService:
statuses=statuses,
)
async def update_workflow(
async def update_workflow_definition(
self,
workflow_id: str,
organization_id: str | None = None,
title: str | None = None,
description: str | None = None,
workflow_definition: WorkflowDefinition | None = None,
delete_script: bool = True,
) -> Workflow:
if workflow_definition:
workflow_definition.validate()
# Update the workflow
updated_workflow = await app.DATABASE.update_workflow(
workflow_id=workflow_id,
title=title,
@@ -946,49 +949,61 @@ class WorkflowService:
description=description,
workflow_definition=(workflow_definition.model_dump() if workflow_definition else None),
)
updated_version = updated_workflow.version
previous_workflow = None
if updated_version > 1:
previous_workflow = await app.DATABASE.get_workflow_by_permanent_id(
workflow_permanent_id=updated_workflow.workflow_permanent_id,
organization_id=organization_id,
version=updated_version - 1,
)
# Check if workflow definition changed and delete published workflow scripts if so
if (
delete_script
and workflow_definition
and previous_workflow
and organization_id
and _get_workflow_definition_without_dates(previous_workflow.workflow_definition)
!= _get_workflow_definition_without_dates(workflow_definition)
):
try:
deleted_count = await app.DATABASE.delete_workflow_scripts_by_permanent_id(
organization_id=organization_id,
workflow_permanent_id=updated_workflow.workflow_permanent_id,
)
if deleted_count > 0:
LOG.info(
"Deleted published workflow scripts due to workflow definition change",
workflow_id=workflow_id,
workflow_permanent_id=updated_workflow.workflow_permanent_id,
organization_id=organization_id,
deleted_count=deleted_count,
)
except Exception as e:
LOG.error(
"Failed to delete published workflow scripts after workflow definition change",
workflow_id=workflow_id,
workflow_permanent_id=updated_workflow.workflow_permanent_id,
organization_id=organization_id,
error=str(e),
exc_info=True,
)
return updated_workflow
async def maybe_delete_cached_code(
self,
workflow: Workflow,
workflow_definition: WorkflowDefinition,
organization_id: str,
delete_script: bool = True,
delete_code_cache_is_ok: bool = False,
) -> None:
if workflow_definition:
workflow_definition.validate()
previous_valid_workflow = await app.DATABASE.get_workflow_by_permanent_id(
workflow_permanent_id=workflow.workflow_permanent_id,
organization_id=organization_id,
exclude_deleted=True,
ignore_version=workflow.version,
)
if previous_valid_workflow:
current_definition = _get_workflow_definition_core_data(previous_valid_workflow.workflow_definition)
new_definition = _get_workflow_definition_core_data(workflow_definition)
has_changes = current_definition != new_definition
else:
has_changes = False
if previous_valid_workflow and has_changes and delete_script:
to_delete = await app.DATABASE.get_workflow_scripts_by_permanent_id(
organization_id=organization_id,
workflow_permanent_id=previous_valid_workflow.workflow_permanent_id,
)
if len(to_delete) > 0:
if not delete_code_cache_is_ok:
raise CannotUpdateWorkflowDueToCodeCache(
workflow_permanent_id=previous_valid_workflow.workflow_permanent_id,
)
else:
try:
await app.DATABASE.delete_workflow_scripts_by_permanent_id(
organization_id=organization_id,
workflow_permanent_id=previous_valid_workflow.workflow_permanent_id,
)
except Exception as e:
LOG.error(
"Failed to delete published workflow scripts after workflow definition change",
workflow_id=workflow.workflow_id,
workflow_permanent_id=previous_valid_workflow.workflow_permanent_id,
organization_id=organization_id,
error=str(e),
exc_info=True,
)
async def delete_workflow_by_permanent_id(
self,
workflow_permanent_id: str,
@@ -1857,12 +1872,208 @@ class WorkflowService:
await self.persist_har_data(browser_state, last_step, workflow, workflow_run)
await self.persist_tracing_data(browser_state, last_step, workflow_run)
async def make_workflow_definition(
self,
workflow_id: str,
workflow_definition_yaml: WorkflowDefinitionYAML,
title: str,
organization_id: str,
) -> WorkflowDefinition:
# Create parameters from the request
parameters: dict[str, PARAMETER_TYPE] = {}
duplicate_parameter_keys = set()
# Check if user's trying to manually create an output parameter
if any(parameter.parameter_type == ParameterType.OUTPUT for parameter in workflow_definition_yaml.parameters):
raise InvalidWorkflowDefinition(message="Cannot manually create output parameters")
# Check if any parameter keys collide with automatically created output parameter keys
block_labels = [block.label for block in workflow_definition_yaml.blocks]
# TODO (kerem): Check if block labels are unique
output_parameter_keys = [f"{block_label}_output" for block_label in block_labels]
parameter_keys = [parameter.key for parameter in workflow_definition_yaml.parameters]
if any(key in output_parameter_keys for key in parameter_keys):
raise WorkflowDefinitionHasReservedParameterKeys(
reserved_keys=output_parameter_keys, parameter_keys=parameter_keys
)
if any(key in RESERVED_PARAMETER_KEYS for key in parameter_keys):
raise WorkflowDefinitionHasReservedParameterKeys(
reserved_keys=RESERVED_PARAMETER_KEYS,
parameter_keys=parameter_keys,
)
# Create output parameters for all blocks
block_output_parameters = await WorkflowService._create_all_output_parameters_for_workflow(
workflow_id=workflow_id,
block_yamls=workflow_definition_yaml.blocks,
)
for block_output_parameter in block_output_parameters.values():
parameters[block_output_parameter.key] = block_output_parameter
# We're going to process context parameters after other parameters since they depend on the other parameters
context_parameter_yamls = []
for parameter in workflow_definition_yaml.parameters:
if parameter.key in parameters:
LOG.error(f"Duplicate parameter key {parameter.key}")
duplicate_parameter_keys.add(parameter.key)
continue
if parameter.parameter_type == ParameterType.AWS_SECRET:
parameters[parameter.key] = await self.create_aws_secret_parameter(
workflow_id=workflow_id,
aws_key=parameter.aws_key,
key=parameter.key,
description=parameter.description,
)
elif parameter.parameter_type == ParameterType.CREDENTIAL:
parameters[parameter.key] = await self.create_credential_parameter(
workflow_id=workflow_id,
key=parameter.key,
description=parameter.description,
credential_id=parameter.credential_id,
)
elif parameter.parameter_type == ParameterType.ONEPASSWORD:
parameters[parameter.key] = await self.create_onepassword_credential_parameter(
workflow_id=workflow_id,
key=parameter.key,
description=parameter.description,
vault_id=parameter.vault_id,
item_id=parameter.item_id,
)
elif parameter.parameter_type == ParameterType.AZURE_VAULT_CREDENTIAL:
parameters[parameter.key] = await self.create_azure_vault_credential_parameter(
workflow_id=workflow_id,
key=parameter.key,
description=parameter.description,
vault_name=parameter.vault_name,
username_key=parameter.username_key,
password_key=parameter.password_key,
totp_secret_key=parameter.totp_secret_key,
)
elif parameter.parameter_type == ParameterType.BITWARDEN_LOGIN_CREDENTIAL:
if not parameter.bitwarden_collection_id and not parameter.bitwarden_item_id:
raise WorkflowParameterMissingRequiredValue(
workflow_parameter_type=ParameterType.BITWARDEN_LOGIN_CREDENTIAL,
workflow_parameter_key=parameter.key,
required_value="bitwarden_collection_id or bitwarden_item_id",
)
if parameter.bitwarden_collection_id and not parameter.url_parameter_key:
raise WorkflowParameterMissingRequiredValue(
workflow_parameter_type=ParameterType.BITWARDEN_LOGIN_CREDENTIAL,
workflow_parameter_key=parameter.key,
required_value="url_parameter_key",
)
parameters[parameter.key] = await self.create_bitwarden_login_credential_parameter(
workflow_id=workflow_id,
bitwarden_client_id_aws_secret_key=parameter.bitwarden_client_id_aws_secret_key,
bitwarden_client_secret_aws_secret_key=parameter.bitwarden_client_secret_aws_secret_key,
bitwarden_master_password_aws_secret_key=parameter.bitwarden_master_password_aws_secret_key,
url_parameter_key=parameter.url_parameter_key,
key=parameter.key,
description=parameter.description,
bitwarden_collection_id=parameter.bitwarden_collection_id,
bitwarden_item_id=parameter.bitwarden_item_id,
)
elif parameter.parameter_type == ParameterType.BITWARDEN_SENSITIVE_INFORMATION:
parameters[parameter.key] = await self.create_bitwarden_sensitive_information_parameter(
workflow_id=workflow_id,
bitwarden_client_id_aws_secret_key=parameter.bitwarden_client_id_aws_secret_key,
bitwarden_client_secret_aws_secret_key=parameter.bitwarden_client_secret_aws_secret_key,
bitwarden_master_password_aws_secret_key=parameter.bitwarden_master_password_aws_secret_key,
# TODO: remove "# type: ignore" after ensuring bitwarden_collection_id is always set
bitwarden_collection_id=parameter.bitwarden_collection_id, # type: ignore
bitwarden_identity_key=parameter.bitwarden_identity_key,
bitwarden_identity_fields=parameter.bitwarden_identity_fields,
key=parameter.key,
description=parameter.description,
)
elif parameter.parameter_type == ParameterType.BITWARDEN_CREDIT_CARD_DATA:
parameters[parameter.key] = await self.create_bitwarden_credit_card_data_parameter(
workflow_id=workflow_id,
bitwarden_client_id_aws_secret_key=parameter.bitwarden_client_id_aws_secret_key,
bitwarden_client_secret_aws_secret_key=parameter.bitwarden_client_secret_aws_secret_key,
bitwarden_master_password_aws_secret_key=parameter.bitwarden_master_password_aws_secret_key,
# TODO: remove "# type: ignore" after ensuring bitwarden_collection_id is always set
bitwarden_collection_id=parameter.bitwarden_collection_id, # type: ignore
bitwarden_item_id=parameter.bitwarden_item_id, # type: ignore
key=parameter.key,
description=parameter.description,
)
elif parameter.parameter_type == ParameterType.WORKFLOW:
parameters[parameter.key] = await self.create_workflow_parameter(
workflow_id=workflow_id,
workflow_parameter_type=parameter.workflow_parameter_type,
key=parameter.key,
default_value=parameter.default_value,
description=parameter.description,
)
elif parameter.parameter_type == ParameterType.OUTPUT:
parameters[parameter.key] = await self.create_output_parameter(
workflow_id=workflow_id,
key=parameter.key,
description=parameter.description,
)
elif parameter.parameter_type == ParameterType.CONTEXT:
context_parameter_yamls.append(parameter)
else:
LOG.error(f"Invalid parameter type {parameter.parameter_type}")
# Now we can process the context parameters since all other parameters have been created
for context_parameter in context_parameter_yamls:
if context_parameter.source_parameter_key not in parameters:
raise ContextParameterSourceNotDefined(
context_parameter_key=context_parameter.key,
source_key=context_parameter.source_parameter_key,
)
if context_parameter.key in parameters:
LOG.error(f"Duplicate parameter key {context_parameter.key}")
duplicate_parameter_keys.add(context_parameter.key)
continue
# We're only adding the context parameter to the parameters dict, we're not creating it in the database
# It'll only be stored in the `workflow.workflow_definition`
# todo (kerem): should we have a database table for context parameters?
parameters[context_parameter.key] = ContextParameter(
key=context_parameter.key,
description=context_parameter.description,
source=parameters[context_parameter.source_parameter_key],
# Context parameters don't have a default value, the value always depends on the source parameter
value=None,
)
if duplicate_parameter_keys:
raise WorkflowDefinitionHasDuplicateParameterKeys(duplicate_keys=duplicate_parameter_keys)
# Create blocks from the request
block_label_mapping = {}
blocks: list[BlockTypeVar] = []
for block_yaml in workflow_definition_yaml.blocks:
block = await self.block_yaml_to_block(block_yaml, parameters)
blocks.append(block)
block_label_mapping[block.label] = block
# Set the blocks for the workflow definition
workflow_definition = WorkflowDefinition(parameters=parameters.values(), blocks=blocks)
LOG.info(
f"Created workflow from request, title: {title}",
parameter_keys=[parameter.key for parameter in parameters.values()],
block_labels=[block.label for block in blocks],
organization_id=organization_id,
title=title,
workflow_id=workflow_id,
)
return workflow_definition
async def create_workflow_from_request(
self,
organization: Organization,
request: WorkflowCreateYAMLRequest,
workflow_permanent_id: str | None = None,
delete_script: bool = True,
delete_code_cache_is_ok: bool = True,
) -> Workflow:
organization_id = organization.organization_id
LOG.info(
@@ -1871,6 +2082,7 @@ class WorkflowService:
title=request.title,
)
new_workflow_id: str | None = None
try:
if workflow_permanent_id:
existing_latest_workflow = await self.get_workflow_by_permanent_id(
@@ -1879,7 +2091,9 @@ class WorkflowService:
exclude_deleted=False,
)
existing_version = existing_latest_workflow.version
workflow = await self.create_workflow(
# NOTE: it's only potential, as it may be immediately deleted!
potential_workflow = await self.create_workflow(
title=request.title,
workflow_definition=WorkflowDefinition(parameters=[], blocks=[]),
description=request.description,
@@ -1903,7 +2117,8 @@ class WorkflowService:
sequential_key=request.sequential_key,
)
else:
workflow = await self.create_workflow(
# NOTE: it's only potential, as it may be immediately deleted!
potential_workflow = await self.create_workflow(
title=request.title,
workflow_definition=WorkflowDefinition(parameters=[], blocks=[]),
description=request.description,
@@ -1925,200 +2140,32 @@ class WorkflowService:
sequential_key=request.sequential_key,
)
# Keeping track of the new workflow id to delete it if an error occurs during the creation process
new_workflow_id = workflow.workflow_id
# Create parameters from the request
parameters: dict[str, PARAMETER_TYPE] = {}
duplicate_parameter_keys = set()
new_workflow_id = potential_workflow.workflow_id
# Check if user's trying to manually create an output parameter
if any(
parameter.parameter_type == ParameterType.OUTPUT for parameter in request.workflow_definition.parameters
):
raise InvalidWorkflowDefinition(message="Cannot manually create output parameters")
# Check if any parameter keys collide with automatically created output parameter keys
block_labels = [block.label for block in request.workflow_definition.blocks]
# TODO (kerem): Check if block labels are unique
output_parameter_keys = [f"{block_label}_output" for block_label in block_labels]
parameter_keys = [parameter.key for parameter in request.workflow_definition.parameters]
if any(key in output_parameter_keys for key in parameter_keys):
raise WorkflowDefinitionHasReservedParameterKeys(
reserved_keys=output_parameter_keys, parameter_keys=parameter_keys
)
if any(key in RESERVED_PARAMETER_KEYS for key in parameter_keys):
raise WorkflowDefinitionHasReservedParameterKeys(
reserved_keys=RESERVED_PARAMETER_KEYS,
parameter_keys=parameter_keys,
)
# Create output parameters for all blocks
block_output_parameters = await WorkflowService._create_all_output_parameters_for_workflow(
workflow_id=workflow.workflow_id,
block_yamls=request.workflow_definition.blocks,
workflow_definition = await self.make_workflow_definition(
potential_workflow.workflow_id,
request.workflow_definition,
request.title,
organization_id,
)
for block_output_parameter in block_output_parameters.values():
parameters[block_output_parameter.key] = block_output_parameter
# We're going to process context parameters after other parameters since they depend on the other parameters
context_parameter_yamls = []
for parameter in request.workflow_definition.parameters:
if parameter.key in parameters:
LOG.error(f"Duplicate parameter key {parameter.key}")
duplicate_parameter_keys.add(parameter.key)
continue
if parameter.parameter_type == ParameterType.AWS_SECRET:
parameters[parameter.key] = await self.create_aws_secret_parameter(
workflow_id=workflow.workflow_id,
aws_key=parameter.aws_key,
key=parameter.key,
description=parameter.description,
)
elif parameter.parameter_type == ParameterType.CREDENTIAL:
parameters[parameter.key] = await self.create_credential_parameter(
workflow_id=workflow.workflow_id,
key=parameter.key,
description=parameter.description,
credential_id=parameter.credential_id,
)
elif parameter.parameter_type == ParameterType.ONEPASSWORD:
parameters[parameter.key] = await self.create_onepassword_credential_parameter(
workflow_id=workflow.workflow_id,
key=parameter.key,
description=parameter.description,
vault_id=parameter.vault_id,
item_id=parameter.item_id,
)
elif parameter.parameter_type == ParameterType.AZURE_VAULT_CREDENTIAL:
parameters[parameter.key] = await self.create_azure_vault_credential_parameter(
workflow_id=workflow.workflow_id,
key=parameter.key,
description=parameter.description,
vault_name=parameter.vault_name,
username_key=parameter.username_key,
password_key=parameter.password_key,
totp_secret_key=parameter.totp_secret_key,
)
elif parameter.parameter_type == ParameterType.BITWARDEN_LOGIN_CREDENTIAL:
if not parameter.bitwarden_collection_id and not parameter.bitwarden_item_id:
raise WorkflowParameterMissingRequiredValue(
workflow_parameter_type=ParameterType.BITWARDEN_LOGIN_CREDENTIAL,
workflow_parameter_key=parameter.key,
required_value="bitwarden_collection_id or bitwarden_item_id",
)
if parameter.bitwarden_collection_id and not parameter.url_parameter_key:
raise WorkflowParameterMissingRequiredValue(
workflow_parameter_type=ParameterType.BITWARDEN_LOGIN_CREDENTIAL,
workflow_parameter_key=parameter.key,
required_value="url_parameter_key",
)
parameters[parameter.key] = await self.create_bitwarden_login_credential_parameter(
workflow_id=workflow.workflow_id,
bitwarden_client_id_aws_secret_key=parameter.bitwarden_client_id_aws_secret_key,
bitwarden_client_secret_aws_secret_key=parameter.bitwarden_client_secret_aws_secret_key,
bitwarden_master_password_aws_secret_key=parameter.bitwarden_master_password_aws_secret_key,
url_parameter_key=parameter.url_parameter_key,
key=parameter.key,
description=parameter.description,
bitwarden_collection_id=parameter.bitwarden_collection_id,
bitwarden_item_id=parameter.bitwarden_item_id,
)
elif parameter.parameter_type == ParameterType.BITWARDEN_SENSITIVE_INFORMATION:
parameters[parameter.key] = await self.create_bitwarden_sensitive_information_parameter(
workflow_id=workflow.workflow_id,
bitwarden_client_id_aws_secret_key=parameter.bitwarden_client_id_aws_secret_key,
bitwarden_client_secret_aws_secret_key=parameter.bitwarden_client_secret_aws_secret_key,
bitwarden_master_password_aws_secret_key=parameter.bitwarden_master_password_aws_secret_key,
# TODO: remove "# type: ignore" after ensuring bitwarden_collection_id is always set
bitwarden_collection_id=parameter.bitwarden_collection_id, # type: ignore
bitwarden_identity_key=parameter.bitwarden_identity_key,
bitwarden_identity_fields=parameter.bitwarden_identity_fields,
key=parameter.key,
description=parameter.description,
)
elif parameter.parameter_type == ParameterType.BITWARDEN_CREDIT_CARD_DATA:
parameters[parameter.key] = await self.create_bitwarden_credit_card_data_parameter(
workflow_id=workflow.workflow_id,
bitwarden_client_id_aws_secret_key=parameter.bitwarden_client_id_aws_secret_key,
bitwarden_client_secret_aws_secret_key=parameter.bitwarden_client_secret_aws_secret_key,
bitwarden_master_password_aws_secret_key=parameter.bitwarden_master_password_aws_secret_key,
# TODO: remove "# type: ignore" after ensuring bitwarden_collection_id is always set
bitwarden_collection_id=parameter.bitwarden_collection_id, # type: ignore
bitwarden_item_id=parameter.bitwarden_item_id, # type: ignore
key=parameter.key,
description=parameter.description,
)
elif parameter.parameter_type == ParameterType.WORKFLOW:
parameters[parameter.key] = await self.create_workflow_parameter(
workflow_id=workflow.workflow_id,
workflow_parameter_type=parameter.workflow_parameter_type,
key=parameter.key,
default_value=parameter.default_value,
description=parameter.description,
)
elif parameter.parameter_type == ParameterType.OUTPUT:
parameters[parameter.key] = await self.create_output_parameter(
workflow_id=workflow.workflow_id,
key=parameter.key,
description=parameter.description,
)
elif parameter.parameter_type == ParameterType.CONTEXT:
context_parameter_yamls.append(parameter)
else:
LOG.error(f"Invalid parameter type {parameter.parameter_type}")
# Now we can process the context parameters since all other parameters have been created
for context_parameter in context_parameter_yamls:
if context_parameter.source_parameter_key not in parameters:
raise ContextParameterSourceNotDefined(
context_parameter_key=context_parameter.key,
source_key=context_parameter.source_parameter_key,
)
if context_parameter.key in parameters:
LOG.error(f"Duplicate parameter key {context_parameter.key}")
duplicate_parameter_keys.add(context_parameter.key)
continue
# We're only adding the context parameter to the parameters dict, we're not creating it in the database
# It'll only be stored in the `workflow.workflow_definition`
# todo (kerem): should we have a database table for context parameters?
parameters[context_parameter.key] = ContextParameter(
key=context_parameter.key,
description=context_parameter.description,
source=parameters[context_parameter.source_parameter_key],
# Context parameters don't have a default value, the value always depends on the source parameter
value=None,
)
if duplicate_parameter_keys:
raise WorkflowDefinitionHasDuplicateParameterKeys(duplicate_keys=duplicate_parameter_keys)
# Create blocks from the request
block_label_mapping = {}
blocks: list[BlockTypeVar] = []
for block_yaml in request.workflow_definition.blocks:
block = await self.block_yaml_to_block(workflow, block_yaml, parameters)
blocks.append(block)
block_label_mapping[block.label] = block
# Set the blocks for the workflow definition
workflow_definition = WorkflowDefinition(parameters=parameters.values(), blocks=blocks)
workflow = await self.update_workflow(
workflow_id=workflow.workflow_id,
organization_id=organization_id,
workflow_definition=workflow_definition,
delete_script=delete_script,
)
LOG.info(
f"Created workflow from request, title: {request.title}",
parameter_keys=[parameter.key for parameter in parameters.values()],
block_labels=[block.label for block in blocks],
updated_workflow = await self.update_workflow_definition(
workflow_id=potential_workflow.workflow_id,
organization_id=organization_id,
title=request.title,
workflow_id=workflow.workflow_id,
description=request.description,
workflow_definition=workflow_definition,
)
return workflow
await self.maybe_delete_cached_code(
updated_workflow,
workflow_definition=workflow_definition,
organization_id=organization_id,
delete_script=delete_script,
delete_code_cache_is_ok=delete_code_cache_is_ok,
)
return updated_workflow
except Exception as e:
if new_workflow_id:
LOG.error(
@@ -2160,7 +2207,6 @@ class WorkflowService:
@staticmethod
async def block_yaml_to_block(
workflow: Workflow,
block_yaml: BLOCK_YAML_TYPES,
parameters: dict[str, Parameter],
) -> BlockTypeVar:
@@ -2198,7 +2244,7 @@ class WorkflowService:
)
elif block_yaml.block_type == BlockType.FOR_LOOP:
loop_blocks = [
await WorkflowService.block_yaml_to_block(workflow, loop_block, parameters)
await WorkflowService.block_yaml_to_block(loop_block, parameters)
for loop_block in block_yaml.loop_blocks
]