diff --git a/skyvern-frontend/src/routes/workflows/editor/helpContent.ts b/skyvern-frontend/src/routes/workflows/editor/helpContent.ts index 37377740..08e39292 100644 --- a/skyvern-frontend/src/routes/workflows/editor/helpContent.ts +++ b/skyvern-frontend/src/routes/workflows/editor/helpContent.ts @@ -14,7 +14,7 @@ export const baseHelpTooltipContent = { completeOnDownload: "Allow Skyvern to auto-complete the block when it downloads a file.", fileSuffix: - "A file suffix that's automatically added to all downloaded files.", + "The complete filename (without extension) for downloaded files. This replaces the entire filename instead of being appended to a random name.", errorCodeMapping: "Knowing about why a block terminated can be important, specify error messages here.", totpVerificationUrl: @@ -34,7 +34,7 @@ export const basePlaceholderContent = { dataExtractionGoal: "What data do you need to extract?", maxRetries: "Default: 3", maxStepsOverride: "Default: 10", - downloadSuffix: "Add an ID for downloaded files", + downloadSuffix: "Enter the complete filename (without extension)", totpVerificationUrl: "Provide your 2FA endpoint", totpIdentifier: "Add an ID that links your TOTP to the block", }; diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/ActionNode/ActionNode.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/ActionNode/ActionNode.tsx index 95ebaa43..59b89bba 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/ActionNode/ActionNode.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/ActionNode/ActionNode.tsx @@ -342,7 +342,7 @@ function ActionNode({ id, data, type }: NodeProps) {
) {
) { className="nopan text-xs" />
+
+
+ + +
+ { + handleChange("path", value); + }} + value={inputs.path as string} + className="nopan text-xs" + /> +
)}
diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/FileUploadNode/types.ts b/skyvern-frontend/src/routes/workflows/editor/nodes/FileUploadNode/types.ts index bd3ec1e8..6dc6ea03 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/FileUploadNode/types.ts +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/FileUploadNode/types.ts @@ -22,7 +22,7 @@ export const fileUploadNodeDefaultData: FileUploadNodeData = { editable: true, storageType: "s3", label: "", - path: "", + path: "{{ workflow_run_id }}", s3Bucket: null, awsAccessKeyId: null, awsSecretAccessKey: null, diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/NavigationNode/NavigationNode.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/NavigationNode/NavigationNode.tsx index 3cacc0e3..f3210423 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/NavigationNode/NavigationNode.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/NavigationNode/NavigationNode.tsx @@ -402,7 +402,7 @@ function NavigationNode({ id, data, type }: NodeProps) {
) {
Self: # key is label name - workflow_run_context = cls(aws_client=aws_client) + workflow_run_context = cls( + workflow_title=workflow_title, + workflow_id=workflow_id, + workflow_permanent_id=workflow_permanent_id, + workflow_run_id=workflow_run_id, + aws_client=aws_client, + ) + for parameter, run_parameter in workflow_parameter_tuples: if parameter.workflow_parameter_type == WorkflowParameterType.CREDENTIAL_ID: await workflow_run_context.register_secret_workflow_parameter_value( @@ -133,7 +144,18 @@ class WorkflowRunContext: return workflow_run_context - def __init__(self, aws_client: AsyncAWSClient) -> None: + def __init__( + self, + workflow_title: str, + workflow_id: str, + workflow_permanent_id: str, + workflow_run_id: str, + aws_client: AsyncAWSClient, + ) -> None: + self.workflow_title = workflow_title + self.workflow_id = workflow_id + self.workflow_permanent_id = workflow_permanent_id + self.workflow_run_id = workflow_run_id self.blocks_metadata: dict[str, BlockMetadata] = {} self.parameters: dict[str, PARAMETER_TYPE] = {} self.values: dict[str, Any] = {} @@ -953,6 +975,9 @@ class WorkflowContextManager: self, organization: Organization, workflow_run_id: str, + workflow_title: str, + workflow_id: str, + workflow_permanent_id: str, workflow_parameter_tuples: list[tuple[WorkflowParameter, "WorkflowRunParameter"]], workflow_output_parameters: list[OutputParameter], context_parameters: list[ContextParameter], @@ -967,6 +992,10 @@ class WorkflowContextManager: workflow_run_context = await WorkflowRunContext.init( self.aws_client, organization, + workflow_run_id, + workflow_title, + workflow_id, + workflow_permanent_id, workflow_parameter_tuples, workflow_output_parameters, context_parameters, diff --git a/skyvern/forge/sdk/workflow/models/block.py b/skyvern/forge/sdk/workflow/models/block.py index 9cbac9dd..dcf9f5ef 100644 --- a/skyvern/forge/sdk/workflow/models/block.py +++ b/skyvern/forge/sdk/workflow/models/block.py @@ -196,6 +196,7 @@ class Block(BaseModel, abc.ABC): template_data[self.label] = block_reference_data + # TODO (suchintan): This is pretty hacky - we should have a standard way to initialize the workflow run context # inject the forloop metadata as global variables if "current_index" in block_reference_data: template_data["current_index"] = block_reference_data["current_index"] @@ -204,6 +205,16 @@ class Block(BaseModel, abc.ABC): if "current_value" in block_reference_data: template_data["current_value"] = block_reference_data["current_value"] + # Initialize workflow-level parameters + if "workflow_title" not in template_data: + template_data["workflow_title"] = workflow_run_context.workflow_title + if "workflow_id" not in template_data: + template_data["workflow_id"] = workflow_run_context.workflow_id + if "workflow_permanent_id" not in template_data: + template_data["workflow_permanent_id"] = workflow_run_context.workflow_permanent_id + if "workflow_run_id" not in template_data: + template_data["workflow_run_id"] = workflow_run_context.workflow_run_id + return template.render(template_data) @classmethod @@ -1933,6 +1944,7 @@ class FileUploadBlock(Block): def format_potential_template_parameters(self, workflow_run_context: WorkflowRunContext) -> None: if self.path: self.path = self.format_block_parameter_template_from_workflow_run_context(self.path, workflow_run_context) + if self.s3_bucket: self.s3_bucket = self.format_block_parameter_template_from_workflow_run_context( self.s3_bucket, workflow_run_context @@ -1959,14 +1971,14 @@ class FileUploadBlock(Block): ) def _get_s3_uri(self, workflow_run_id: str, path: str) -> str: - s3_suffix = f"{workflow_run_id}/{uuid.uuid4()}_{Path(path).name}" - if not self.path: - return f"s3://{self.s3_bucket}/{s3_suffix}" - return f"s3://{self.s3_bucket}/{self.path}/{s3_suffix}" + folder_path = self.path or f"{workflow_run_id}" + s3_suffix = f"{uuid.uuid4()}_{Path(path).name}" + return f"s3://{self.s3_bucket}/{folder_path}/{s3_suffix}" def _get_azure_blob_uri(self, workflow_run_id: str, file_path: str) -> str: blob_name = Path(file_path).name - return f"https://{self.azure_storage_account_name}.blob.core.windows.net/{self.azure_blob_container_name}/{workflow_run_id}/{uuid.uuid4()}_{blob_name}" + folder_path = self.path or workflow_run_id + return f"https://{self.azure_storage_account_name}.blob.core.windows.net/{self.azure_blob_container_name}/{folder_path}/{uuid.uuid4()}_{blob_name}" async def execute( self, diff --git a/skyvern/forge/sdk/workflow/models/parameter.py b/skyvern/forge/sdk/workflow/models/parameter.py index db071cbc..45939f99 100644 --- a/skyvern/forge/sdk/workflow/models/parameter.py +++ b/skyvern/forge/sdk/workflow/models/parameter.py @@ -8,7 +8,15 @@ from pydantic import BaseModel, ConfigDict, Field from skyvern.exceptions import InvalidWorkflowParameter -RESERVED_PARAMETER_KEYS = ["current_item", "current_value", "current_index"] +RESERVED_PARAMETER_KEYS = [ + "current_item", + "current_value", + "current_index", + "workflow_title", + "workflow_id", + "workflow_permanent_id", + "workflow_run_id", +] class ParameterType(StrEnum): diff --git a/skyvern/forge/sdk/workflow/service.py b/skyvern/forge/sdk/workflow/service.py index 345bfda8..1a4041ee 100644 --- a/skyvern/forge/sdk/workflow/service.py +++ b/skyvern/forge/sdk/workflow/service.py @@ -320,6 +320,9 @@ class WorkflowService: await app.WORKFLOW_CONTEXT_MANAGER.initialize_workflow_run_context( organization, workflow_run_id, + workflow.title, + workflow.workflow_id, + workflow.workflow_permanent_id, wp_wps_tuples, workflow_output_parameters, context_parameters, diff --git a/skyvern/schemas/workflows.py b/skyvern/schemas/workflows.py index f63c396f..e47f89a8 100644 --- a/skyvern/schemas/workflows.py +++ b/skyvern/schemas/workflows.py @@ -210,7 +210,9 @@ class TaskBlockYAML(BlockYAML): max_steps_per_run: int | None = None parameter_keys: list[str] | None = None complete_on_download: bool = False - download_suffix: str | None = None + download_suffix: str | None = ( + None # DEPRECATED: This field now sets the complete filename instead of appending to a random name + ) totp_verification_url: str | None = None totp_identifier: str | None = None cache_actions: bool = False @@ -287,6 +289,7 @@ class FileUploadBlockYAML(BlockYAML): azure_storage_account_name: str | None = None azure_storage_account_key: str | None = None azure_blob_container_name: str | None = None + azure_folder_path: str | None = None path: str | None = None @@ -343,7 +346,9 @@ class ActionBlockYAML(BlockYAML): max_retries: int = 0 parameter_keys: list[str] | None = None complete_on_download: bool = False - download_suffix: str | None = None + download_suffix: str | None = ( + None # DEPRECATED: This field now sets the complete filename instead of appending to a random name + ) totp_verification_url: str | None = None totp_identifier: str | None = None cache_actions: bool = False @@ -361,7 +366,9 @@ class NavigationBlockYAML(BlockYAML): max_steps_per_run: int | None = None parameter_keys: list[str] | None = None complete_on_download: bool = False - download_suffix: str | None = None + download_suffix: str | None = ( + None # DEPRECATED: This field now sets the complete filename instead of appending to a random name + ) totp_verification_url: str | None = None totp_identifier: str | None = None cache_actions: bool = False @@ -420,7 +427,9 @@ class FileDownloadBlockYAML(BlockYAML): max_retries: int = 0 max_steps_per_run: int | None = None parameter_keys: list[str] | None = None - download_suffix: str | None = None + download_suffix: str | None = ( + None # DEPRECATED: This field now sets the complete filename instead of appending to a random name + ) totp_verification_url: str | None = None totp_identifier: str | None = None cache_actions: bool = False diff --git a/skyvern/services/task_v2_service.py b/skyvern/services/task_v2_service.py index b8bf2a51..faa3ec85 100644 --- a/skyvern/services/task_v2_service.py +++ b/skyvern/services/task_v2_service.py @@ -489,7 +489,9 @@ async def run_task_v2_helper( task_v2_id=task_v2_id, organization_id=organization_id, status=TaskV2Status.running ) await app.WORKFLOW_SERVICE.mark_workflow_run_as_running(workflow_run_id=workflow_run.workflow_run_id) - await _set_up_workflow_context(workflow_id, workflow_run_id, organization) + + workflow = await app.WORKFLOW_SERVICE.get_workflow(workflow_id=workflow_run.workflow_id) + await _set_up_workflow_context(workflow, workflow_run_id, organization) url = str(task_v2.url) user_prompt = task_v2.prompt @@ -1010,16 +1012,21 @@ async def handle_block_result( ) -async def _set_up_workflow_context(workflow_id: str, workflow_run_id: str, organization: Organization) -> None: +async def _set_up_workflow_context(workflow: Workflow, workflow_run_id: str, organization: Organization) -> None: """ TODO: see if we could remove this function as we can just set an empty workflow context """ # Get all tuples wp_wps_tuples = await app.WORKFLOW_SERVICE.get_workflow_run_parameter_tuples(workflow_run_id=workflow_run_id) - workflow_output_parameters = await app.WORKFLOW_SERVICE.get_workflow_output_parameters(workflow_id=workflow_id) + workflow_output_parameters = await app.WORKFLOW_SERVICE.get_workflow_output_parameters( + workflow_id=workflow.workflow_id + ) await app.WORKFLOW_CONTEXT_MANAGER.initialize_workflow_run_context( organization, workflow_run_id, + workflow.title, + workflow.workflow_id, + workflow.workflow_permanent_id, wp_wps_tuples, workflow_output_parameters, [],