From 222f3649d1bf2592e72c2c7ab23010492d07d34b Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Tue, 13 Feb 2024 18:03:57 +0100 Subject: [PATCH 01/57] Start refactoring invoke operation --- lib/galaxy/schema/workflows.py | 81 +++++++++++++++++++ lib/galaxy/webapps/galaxy/api/workflows.py | 30 +++++++ .../webapps/galaxy/services/workflows.py | 70 ++++++++++++++++ lib/galaxy_test/api/test_workflows.py | 1 + 4 files changed, 182 insertions(+) create mode 100644 lib/galaxy/schema/workflows.py diff --git a/lib/galaxy/schema/workflows.py b/lib/galaxy/schema/workflows.py new file mode 100644 index 000000000000..12299686e34b --- /dev/null +++ b/lib/galaxy/schema/workflows.py @@ -0,0 +1,81 @@ +from typing import ( + Any, + Dict, + Optional, +) + +from pydantic import Field + +from galaxy.schema.schema import ( + HistoryID, + Model, + PreferredObjectStoreIdField, +) + +# class WorkflowExtractionParams(Model): +# from_history_id: str = Field(..., title="From History ID", description="Id of history to extract a workflow from.") +# job_ids: Optional[str] = Field( +# None, +# title="Job IDs", +# description="List of jobs to include when extracting a workflow from history", +# ) + +# @field_validator("dataset_ids", mode="before", check_fields=False) +# @classmethod +# def inputs_string_to_json(cls, v): +# if isinstance(v, str): +# return json.loads(v) +# return v + +# dataset_ids: Optional[str] = Field( +# None, +# title="Dataset IDs", +# description="List of HDA 'hid's corresponding to workflow inputs when extracting a workflow from history", +# alias="ds_map", +# ) +# dataset_collection_ids: Optional[str] = Field( +# None, +# title="Dataset Collection IDs", +# description="List of HDCA 'hid's corresponding to workflow inputs when extracting a workflow from history", +# ) +# workflow_name: Optional[str] = Field( +# None, +# title="Workflow Name", +# description="Name of the workflow to create when extracting a workflow from history", +# ) + + +class GetTargetHistoryPayload(Model): + history_id: HistoryID + history_name: Optional[str] = Field( + None, + title="History Name", + description="The name of the history to import the workflow into.", + ) + new_history_name: Optional[str] = Field( + None, + title="New History Name", + description="The name of the new history to import the workflow into.", + ) + + +class InvokeWorkflowPayload(GetTargetHistoryPayload): + allow_tool_state_corrections: Optional[bool] = False + use_cached_job: Optional[bool] = False + step_parameters: Optional[Dict[str, Any]] = None + # input_step_parameters: Dict[str, InvocationInputParameter] = Field( + # default=..., title="Input step parameters", description="Input step parameters of the workflow invocation." + # ) + parameters: Optional[Dict[str, Any]] = None + inputs: Optional[Dict[str, Any]] = None + ds_map: Optional[Dict[str, Any]] = None + no_add_to_history: Optional[bool] = False + legacy: Optional[bool] = False + parameters_normalized: Optional[bool] = False + inputs_by: Optional[str] = None + resource_params: Optional[Dict[str, Any]] = None + replacement_params: Optional[Dict[str, Any]] = None + effective_outputs: Optional[bool] = None + preferred_object_store_id: Optional[str] = PreferredObjectStoreIdField + preferred_intermediate_object_store_id: Optional[str] = None + preferred_outputs_object_store_id: Optional[str] = None diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 7d4df8da2661..9c5f52bf5826 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -74,6 +74,8 @@ SharingStatus, WorkflowSortByEnum, ) + +# from galaxy.schema.workflows import InvokeWorkflowPayload from galaxy.structured_app import StructuredApp from galaxy.tool_shed.galaxy_install.install_manager import InstallRepositoryManager from galaxy.tools import recommendations @@ -968,6 +970,15 @@ def __get_stored_workflow(self, trans, workflow_id, **kwd): description="Set this to true to skip joining workflow step counts and optimize the resulting index query. Response objects will not contain step counts.", ) +# InvokeWorkflowBody = Annotated[ +# InvokeWorkflowPayload, +# Body( +# default=..., +# title="Invoke workflow", +# description="The values to invoke a workflow.", +# ), +# ] + @router.cbv class FastAPIWorkflows: @@ -1124,6 +1135,25 @@ def undelete_workflow( self.service.undelete(trans, workflow_id) return Response(status_code=status.HTTP_204_NO_CONTENT) + # @router.post( + # "/api/workflows/{workflow_id}/invocations", + # name="Invoke workflow.", + # summary="Schedule the workflow specified by `workflow_id` to run.", + # ) + # def invoke( + # self, + # workflow_id: StoredWorkflowIDPathParam, + # payload: InvokeWorkflowBody, + # trans: ProvidesHistoryContext = DependsOnTrans, + # ): + # """ + # .. note:: This method takes the same arguments as + # :func:`galaxy.webapps.galaxy.api.workflows.WorkflowsAPIController.create` above. + + # :raises: exceptions.MessageException, exceptions.RequestParameterInvalidException + # """ + # return self.service.invoke_workflow(trans, workflow_id, payload) + @router.get( "/api/workflows/{workflow_id}/versions", summary="List all versions of a workflow.", diff --git a/lib/galaxy/webapps/galaxy/services/workflows.py b/lib/galaxy/webapps/galaxy/services/workflows.py index 7710d3479b56..99f615872293 100644 --- a/lib/galaxy/webapps/galaxy/services/workflows.py +++ b/lib/galaxy/webapps/galaxy/services/workflows.py @@ -105,6 +105,76 @@ def index( return workflows, total_matches return rval, total_matches + # def invoke_workflow(self, trans, workflow_id, payload): + # # Get workflow + accessibility check. + # stored_workflow = self._workflows_manager.get_stored_accessible_workflow( + # trans, workflow_id, instance=payload.get("instance", False) + # ) + # workflow = stored_workflow.latest_workflow + # run_configs = build_workflow_run_configs(trans, workflow, payload) + # is_batch = payload.get("batch") + # if not is_batch and len(run_configs) != 1: + # raise HTTPException(status_code=400, detail="Must specify 'batch' to use batch parameters.") + + # require_exact_tool_versions = util.string_as_bool(payload.get("require_exact_tool_versions", "true")) + # tools = self._workflow_contents_manager.get_all_tools(workflow) + # missing_tools = [ + # tool + # for tool in tools + # if not self.app.toolbox.has_tool( + # tool["tool_id"], tool_version=tool["tool_version"], exact=require_exact_tool_versions + # ) + # ] + # if missing_tools: + # missing_tools_message = "Workflow was not invoked; the following required tools are not installed: " + # if require_exact_tool_versions: + # missing_tools_message += ", ".join( + # [f"{tool['tool_id']} (version {tool['tool_version']})" for tool in missing_tools] + # ) + # else: + # missing_tools_message += ", ".join([tool["tool_id"] for tool in missing_tools]) + # raise HTTPException(status_code=400, detail=missing_tools_message) + # # if missing_tools: + # # missing_tools_message = "Workflow was not invoked; the following required tools are not installed: " + # # if require_exact_tool_versions: + # # missing_tools_message += ", ".join( + # # [f"{tool['tool_id']} (version {tool['tool_version']})" for tool in missing_tools] + # # ) + # # else: + # # missing_tools_message += ", ".join([tool["tool_id"] for tool in missing_tools]) + # # raise exceptions.MessageException(missing_tools_message) + + # invocations = [] + # for run_config in run_configs: + # workflow_scheduler_id = payload.get("scheduler", None) + # # TODO: workflow scheduler hints + # work_request_params = dict(scheduler=workflow_scheduler_id) + # workflow_invocation = queue_invoke( + # trans=trans, + # workflow=workflow, + # workflow_run_config=run_config, + # request_params=work_request_params, + # flush=False, + # ) + # invocations.append(workflow_invocation) + + # with transaction(trans.sa_session): + # trans.sa_session.commit() + # encoded_invocations = [] + # for invocation in invocations: + # as_dict = workflow_invocation.to_dict() + # as_dict = self.encode_all_ids(trans, as_dict, recursive=True) + # as_dict["messages"] = [ + # InvocationMessageResponseModel.model_validate(message).model_dump(mode="json") + # for message in invocation.messages + # ] + # encoded_invocations.append(as_dict) + + # if is_batch: + # return encoded_invocations + # else: + # return encoded_invocations[0] + def delete(self, trans, workflow_id): workflow_to_delete = self._workflows_manager.get_stored_workflow(trans, workflow_id) self._workflows_manager.check_security(trans, workflow_to_delete) diff --git a/lib/galaxy_test/api/test_workflows.py b/lib/galaxy_test/api/test_workflows.py index aa0d83b3c9e5..86cfa2227812 100644 --- a/lib/galaxy_test/api/test_workflows.py +++ b/lib/galaxy_test/api/test_workflows.py @@ -5172,6 +5172,7 @@ def test_cannot_run_inaccessible_workflow(self): workflow = self.workflow_populator.load_workflow(name="test_for_run_cannot_access") workflow_request, _, workflow_id = self._setup_workflow_run(workflow) with self._different_user(): + # run_workflow_response = self._post(f"workflows/{workflow_id}/invocations", data=workflow_request, json=True) run_workflow_response = self._post(f"workflows/{workflow_id}/invocations", data=workflow_request) self._assert_status_code_is(run_workflow_response, 403) From 409ee08993394cfd3e8037dae9dd97d555e595b8 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Wed, 14 Feb 2024 18:07:28 +0100 Subject: [PATCH 02/57] Rework pydantic models for invoke operation from workflows API --- lib/galaxy/schema/workflows.py | 50 +++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/lib/galaxy/schema/workflows.py b/lib/galaxy/schema/workflows.py index 12299686e34b..1aa13662d336 100644 --- a/lib/galaxy/schema/workflows.py +++ b/lib/galaxy/schema/workflows.py @@ -1,13 +1,16 @@ +import json from typing import ( Any, Dict, Optional, ) -from pydantic import Field +from pydantic import ( + Field, + field_validator, +) from galaxy.schema.schema import ( - HistoryID, Model, PreferredObjectStoreIdField, ) @@ -44,9 +47,20 @@ # description="Name of the workflow to create when extracting a workflow from history", # ) +# TODO - add description to fields + class GetTargetHistoryPayload(Model): - history_id: HistoryID + history: Optional[str] = Field( + None, + title="History", + description="The history to import the workflow into.", + ) + history_id: Optional[str] = Field( + None, + title="History ID", + description="The history to import the workflow into.", + ) history_name: Optional[str] = Field( None, title="History Name", @@ -60,6 +74,26 @@ class GetTargetHistoryPayload(Model): class InvokeWorkflowPayload(GetTargetHistoryPayload): + instance: Optional[bool] = Field( + False, + title="Is Instance", + description="If true, the workflow is invoked as an instance.", + ) + scheduler: Optional[str] = Field( + None, + title="Scheduler", + description="Scheduler to use for workflow invocation.", + ) + batch: Optional[bool] = Field( + False, + title="Batch", + description="If true, the workflow is invoked as a batch.", + ) + require_exact_tool_versions: Optional[bool] = Field( + False, + title="Require Exact Tool Versions", + description="If true, exact tool versions are required for workflow invocation.", + ) allow_tool_state_corrections: Optional[bool] = False use_cached_job: Optional[bool] = False step_parameters: Optional[Dict[str, Any]] = None @@ -68,7 +102,15 @@ class InvokeWorkflowPayload(GetTargetHistoryPayload): # ) parameters: Optional[Dict[str, Any]] = None inputs: Optional[Dict[str, Any]] = None - ds_map: Optional[Dict[str, Any]] = None + + @field_validator("ds_map", mode="before", check_fields=False) + @classmethod + def inputs_string_to_json(cls, v): + if isinstance(v, str): + return json.loads(v) + return v + + ds_map: Optional[Dict[str, Dict[str, Any]]] = None no_add_to_history: Optional[bool] = False legacy: Optional[bool] = False parameters_normalized: Optional[bool] = False From 52958a31c5201884ef0478c99e10daa65bfc7eba Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Wed, 14 Feb 2024 18:08:09 +0100 Subject: [PATCH 03/57] Remove mapping to legacy route --- lib/galaxy/webapps/galaxy/buildapp.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py index 2ff3ea8c2e1e..b561198aa400 100644 --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -690,22 +690,6 @@ def populate_api_routes(webapp, app): # conditions=dict(method=["POST"]), # ) - # API refers to usages and invocations - these mean the same thing but the - # usage routes should be considered deprecated. - invoke_names = { - "invocations": "", - "usage": "_deprecated", - } - for noun, suffix in invoke_names.items(): - name = f"{noun}{suffix}" - webapp.mapper.connect( - f"workflow_{name}", - "/api/workflows/{workflow_id}/%s" % noun, - controller="workflows", - action="invoke", - conditions=dict(method=["POST"]), - ) - # ================================ # ===== USERS API ===== # ================================ From 90d833ac7b2f63af7ae2cd2a4196cc701980cd51 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Wed, 14 Feb 2024 18:09:53 +0100 Subject: [PATCH 04/57] Refactor invoke operation --- lib/galaxy/webapps/galaxy/api/workflows.py | 127 +++++---------------- 1 file changed, 28 insertions(+), 99 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 9c5f52bf5826..643fd20b6efd 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -53,7 +53,6 @@ CreateInvocationFromStore, CreateInvocationsFromStorePayload, InvocationJobsResponse, - InvocationMessageResponseModel, InvocationReport, InvocationSerializationParams, InvocationStep, @@ -74,6 +73,7 @@ SharingStatus, WorkflowSortByEnum, ) +from galaxy.schema.workflows import InvokeWorkflowPayload # from galaxy.schema.workflows import InvokeWorkflowPayload from galaxy.structured_app import StructuredApp @@ -120,8 +120,6 @@ ) from galaxy.workflow.extract import extract_workflow from galaxy.workflow.modules import module_factory -from galaxy.workflow.run import queue_invoke -from galaxy.workflow.run_request import build_workflow_run_configs log = logging.getLogger(__name__) @@ -723,76 +721,6 @@ def __api_import_shared_workflow(self, trans: GalaxyWebTransaction, workflow_id, item["url"] = url_for("workflow", id=encoded_id) return item - @expose_api - def invoke(self, trans: GalaxyWebTransaction, workflow_id, payload, **kwd): - """ - POST /api/workflows/{encoded_workflow_id}/invocations - - Schedule the workflow specified by `workflow_id` to run. - - .. note:: This method takes the same arguments as - :func:`galaxy.webapps.galaxy.api.workflows.WorkflowsAPIController.create` above. - - :raises: exceptions.MessageException, exceptions.RequestParameterInvalidException - """ - # Get workflow + accessibility check. - stored_workflow = self.__get_stored_accessible_workflow(trans, workflow_id, instance=kwd.get("instance", False)) - workflow = stored_workflow.latest_workflow - run_configs = build_workflow_run_configs(trans, workflow, payload) - is_batch = payload.get("batch") - if not is_batch and len(run_configs) != 1: - raise exceptions.RequestParameterInvalidException("Must specify 'batch' to use batch parameters.") - - require_exact_tool_versions = util.string_as_bool(payload.get("require_exact_tool_versions", "true")) - tools = self.workflow_contents_manager.get_all_tools(workflow) - missing_tools = [ - tool - for tool in tools - if not self.app.toolbox.has_tool( - tool["tool_id"], tool_version=tool["tool_version"], exact=require_exact_tool_versions - ) - ] - if missing_tools: - missing_tools_message = "Workflow was not invoked; the following required tools are not installed: " - if require_exact_tool_versions: - missing_tools_message += ", ".join( - [f"{tool['tool_id']} (version {tool['tool_version']})" for tool in missing_tools] - ) - else: - missing_tools_message += ", ".join([tool["tool_id"] for tool in missing_tools]) - raise exceptions.MessageException(missing_tools_message) - - invocations = [] - for run_config in run_configs: - workflow_scheduler_id = payload.get("scheduler", None) - # TODO: workflow scheduler hints - work_request_params = dict(scheduler=workflow_scheduler_id) - workflow_invocation = queue_invoke( - trans=trans, - workflow=workflow, - workflow_run_config=run_config, - request_params=work_request_params, - flush=False, - ) - invocations.append(workflow_invocation) - - with transaction(trans.sa_session): - trans.sa_session.commit() - encoded_invocations = [] - for invocation in invocations: - as_dict = workflow_invocation.to_dict() - as_dict = self.encode_all_ids(trans, as_dict, recursive=True) - as_dict["messages"] = [ - InvocationMessageResponseModel.model_validate(message).model_dump(mode="json") - for message in invocation.messages - ] - encoded_invocations.append(as_dict) - - if is_batch: - return encoded_invocations - else: - return encoded_invocations[0] - def _workflow_from_dict(self, trans, data, workflow_create_options, source=None): """Creates a workflow from a dict. @@ -970,14 +898,14 @@ def __get_stored_workflow(self, trans, workflow_id, **kwd): description="Set this to true to skip joining workflow step counts and optimize the resulting index query. Response objects will not contain step counts.", ) -# InvokeWorkflowBody = Annotated[ -# InvokeWorkflowPayload, -# Body( -# default=..., -# title="Invoke workflow", -# description="The values to invoke a workflow.", -# ), -# ] +InvokeWorkflowBody = Annotated[ + InvokeWorkflowPayload, + Body( + default=..., + title="Invoke workflow", + description="The values to invoke a workflow.", + ), +] @router.cbv @@ -1135,24 +1063,25 @@ def undelete_workflow( self.service.undelete(trans, workflow_id) return Response(status_code=status.HTTP_204_NO_CONTENT) - # @router.post( - # "/api/workflows/{workflow_id}/invocations", - # name="Invoke workflow.", - # summary="Schedule the workflow specified by `workflow_id` to run.", - # ) - # def invoke( - # self, - # workflow_id: StoredWorkflowIDPathParam, - # payload: InvokeWorkflowBody, - # trans: ProvidesHistoryContext = DependsOnTrans, - # ): - # """ - # .. note:: This method takes the same arguments as - # :func:`galaxy.webapps.galaxy.api.workflows.WorkflowsAPIController.create` above. - - # :raises: exceptions.MessageException, exceptions.RequestParameterInvalidException - # """ - # return self.service.invoke_workflow(trans, workflow_id, payload) + @router.post( + "/api/workflows/{workflow_id}/invocations", + name="Invoke workflow.", + summary="Schedule the workflow specified by `workflow_id` to run.", + ) + def invoke( + self, + # workflow_id: StoredWorkflowIDPathParam, + payload: InvokeWorkflowBody, + workflow_id: str = Path(...), + trans: ProvidesHistoryContext = DependsOnTrans, + ) -> Union[WorkflowInvocationResponse, List[WorkflowInvocationResponse]]: + """ + .. note:: This method takes the same arguments as + :func:`galaxy.webapps.galaxy.api.workflows.WorkflowsAPIController.create` above. + + :raises: exceptions.MessageException, exceptions.RequestParameterInvalidException + """ + return self.service.invoke_workflow(trans, workflow_id, payload.model_dump(exclude_unset=True)) @router.get( "/api/workflows/{workflow_id}/versions", From 6ec9013fe33ef61f642724938ed122818f387c9b Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Wed, 14 Feb 2024 18:10:44 +0100 Subject: [PATCH 05/57] Create service method for invoke operation --- .../webapps/galaxy/services/workflows.py | 143 +++++++++--------- 1 file changed, 73 insertions(+), 70 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/services/workflows.py b/lib/galaxy/webapps/galaxy/services/workflows.py index 99f615872293..e5cdbb19dabe 100644 --- a/lib/galaxy/webapps/galaxy/services/workflows.py +++ b/lib/galaxy/webapps/galaxy/services/workflows.py @@ -5,9 +5,15 @@ List, Optional, Tuple, + Union, ) -from galaxy import web +from fastapi import HTTPException + +from galaxy import ( + util, + web, +) from galaxy.managers.context import ProvidesUserContext from galaxy.managers.notification import NotificationManager from galaxy.managers.workflows import ( @@ -16,6 +22,8 @@ WorkflowsManager, ) from galaxy.model import StoredWorkflow +from galaxy.model.base import transaction +from galaxy.schema.invocation import WorkflowInvocationResponse from galaxy.schema.schema import ( InvocationsStateCounts, WorkflowIndexQueryPayload, @@ -23,6 +31,8 @@ from galaxy.util.tool_shed.tool_shed_registry import Registry from galaxy.webapps.galaxy.services.base import ServiceBase from galaxy.webapps.galaxy.services.sharable import ShareableService +from galaxy.workflow.run import queue_invoke +from galaxy.workflow.run_request import build_workflow_run_configs log = logging.getLogger(__name__) @@ -105,75 +115,68 @@ def index( return workflows, total_matches return rval, total_matches - # def invoke_workflow(self, trans, workflow_id, payload): - # # Get workflow + accessibility check. - # stored_workflow = self._workflows_manager.get_stored_accessible_workflow( - # trans, workflow_id, instance=payload.get("instance", False) - # ) - # workflow = stored_workflow.latest_workflow - # run_configs = build_workflow_run_configs(trans, workflow, payload) - # is_batch = payload.get("batch") - # if not is_batch and len(run_configs) != 1: - # raise HTTPException(status_code=400, detail="Must specify 'batch' to use batch parameters.") - - # require_exact_tool_versions = util.string_as_bool(payload.get("require_exact_tool_versions", "true")) - # tools = self._workflow_contents_manager.get_all_tools(workflow) - # missing_tools = [ - # tool - # for tool in tools - # if not self.app.toolbox.has_tool( - # tool["tool_id"], tool_version=tool["tool_version"], exact=require_exact_tool_versions - # ) - # ] - # if missing_tools: - # missing_tools_message = "Workflow was not invoked; the following required tools are not installed: " - # if require_exact_tool_versions: - # missing_tools_message += ", ".join( - # [f"{tool['tool_id']} (version {tool['tool_version']})" for tool in missing_tools] - # ) - # else: - # missing_tools_message += ", ".join([tool["tool_id"] for tool in missing_tools]) - # raise HTTPException(status_code=400, detail=missing_tools_message) - # # if missing_tools: - # # missing_tools_message = "Workflow was not invoked; the following required tools are not installed: " - # # if require_exact_tool_versions: - # # missing_tools_message += ", ".join( - # # [f"{tool['tool_id']} (version {tool['tool_version']})" for tool in missing_tools] - # # ) - # # else: - # # missing_tools_message += ", ".join([tool["tool_id"] for tool in missing_tools]) - # # raise exceptions.MessageException(missing_tools_message) - - # invocations = [] - # for run_config in run_configs: - # workflow_scheduler_id = payload.get("scheduler", None) - # # TODO: workflow scheduler hints - # work_request_params = dict(scheduler=workflow_scheduler_id) - # workflow_invocation = queue_invoke( - # trans=trans, - # workflow=workflow, - # workflow_run_config=run_config, - # request_params=work_request_params, - # flush=False, - # ) - # invocations.append(workflow_invocation) - - # with transaction(trans.sa_session): - # trans.sa_session.commit() - # encoded_invocations = [] - # for invocation in invocations: - # as_dict = workflow_invocation.to_dict() - # as_dict = self.encode_all_ids(trans, as_dict, recursive=True) - # as_dict["messages"] = [ - # InvocationMessageResponseModel.model_validate(message).model_dump(mode="json") - # for message in invocation.messages - # ] - # encoded_invocations.append(as_dict) - - # if is_batch: - # return encoded_invocations - # else: - # return encoded_invocations[0] + def invoke_workflow( + self, trans, workflow_id, payload + ) -> Union[WorkflowInvocationResponse, List[WorkflowInvocationResponse]]: + # TODO - make use of pydantic model for payload + # Get workflow + accessibility check. + by_stored_id = not payload.get("instance", False) + stored_workflow = self._workflows_manager.get_stored_accessible_workflow(trans, workflow_id, by_stored_id) + workflow = stored_workflow.latest_workflow + run_configs = build_workflow_run_configs(trans, workflow, payload) + is_batch = payload.get("batch") + if not is_batch and len(run_configs) != 1: + raise HTTPException(status_code=400, detail="Must specify 'batch' to use batch parameters.") + + require_exact_tool_versions = util.string_as_bool(payload.get("require_exact_tool_versions", "true")) + tools = self._workflow_contents_manager.get_all_tools(workflow) + missing_tools = [ + tool + for tool in tools + if not trans.app.toolbox.has_tool( + tool["tool_id"], tool_version=tool["tool_version"], exact=require_exact_tool_versions + ) + ] + if missing_tools: + missing_tools_message = "Workflow was not invoked; the following required tools are not installed: " + if require_exact_tool_versions: + missing_tools_message += ", ".join( + [f"{tool['tool_id']} (version {tool['tool_version']})" for tool in missing_tools] + ) + else: + missing_tools_message += ", ".join([tool["tool_id"] for tool in missing_tools]) + raise HTTPException(status_code=400, detail=missing_tools_message) + # if missing_tools: + # missing_tools_message = "Workflow was not invoked; the following required tools are not installed: " + # if require_exact_tool_versions: + # missing_tools_message += ", ".join( + # [f"{tool['tool_id']} (version {tool['tool_version']})" for tool in missing_tools] + # ) + # else: + # missing_tools_message += ", ".join([tool["tool_id"] for tool in missing_tools]) + # raise exceptions.MessageException(missing_tools_message) + + invocations = [] + for run_config in run_configs: + workflow_scheduler_id = payload.get("scheduler", None) + # TODO: workflow scheduler hints + work_request_params = dict(scheduler=workflow_scheduler_id) + workflow_invocation = queue_invoke( + trans=trans, + workflow=workflow, + workflow_run_config=run_config, + request_params=work_request_params, + flush=False, + ) + invocations.append(workflow_invocation) + + with transaction(trans.sa_session): + trans.sa_session.commit() + encoded_invocations = [WorkflowInvocationResponse(**invocation.to_dict()) for invocation in invocations] + if is_batch: + return encoded_invocations + else: + return encoded_invocations[0] def delete(self, trans, workflow_id): workflow_to_delete = self._workflows_manager.get_stored_workflow(trans, workflow_id) From 187636123dc5aa36623039ce09f42bf213024d2c Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Wed, 14 Feb 2024 18:12:03 +0100 Subject: [PATCH 06/57] Add json keyword in tests calling invoke endpoint --- lib/galaxy_test/api/test_workflow_extraction.py | 2 +- lib/galaxy_test/api/test_workflows.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/galaxy_test/api/test_workflow_extraction.py b/lib/galaxy_test/api/test_workflow_extraction.py index d57554de6998..4d48887cc30d 100644 --- a/lib/galaxy_test/api/test_workflow_extraction.py +++ b/lib/galaxy_test/api/test_workflow_extraction.py @@ -478,7 +478,7 @@ def __copy_content_to_history(self, history_id, content): def __setup_and_run_cat1_workflow(self, history_id): workflow = self.workflow_populator.load_workflow(name="test_for_extract") workflow_request, history_id, workflow_id = self._setup_workflow_run(workflow, history_id=history_id) - run_workflow_response = self._post(f"workflows/{workflow_id}/invocations", data=workflow_request) + run_workflow_response = self._post(f"workflows/{workflow_id}/invocations", data=workflow_request, json=True) self._assert_status_code_is(run_workflow_response, 200) invocation_response = run_workflow_response.json() self.workflow_populator.wait_for_invocation_and_jobs( diff --git a/lib/galaxy_test/api/test_workflows.py b/lib/galaxy_test/api/test_workflows.py index 86cfa2227812..02d4135f34f8 100644 --- a/lib/galaxy_test/api/test_workflows.py +++ b/lib/galaxy_test/api/test_workflows.py @@ -5172,8 +5172,7 @@ def test_cannot_run_inaccessible_workflow(self): workflow = self.workflow_populator.load_workflow(name="test_for_run_cannot_access") workflow_request, _, workflow_id = self._setup_workflow_run(workflow) with self._different_user(): - # run_workflow_response = self._post(f"workflows/{workflow_id}/invocations", data=workflow_request, json=True) - run_workflow_response = self._post(f"workflows/{workflow_id}/invocations", data=workflow_request) + run_workflow_response = self._post(f"workflows/{workflow_id}/invocations", data=workflow_request, json=True) self._assert_status_code_is(run_workflow_response, 403) def test_400_on_invalid_workflow_id(self): @@ -5188,7 +5187,7 @@ def test_cannot_run_against_other_users_history(self): with self._different_user(): other_history_id = self.dataset_populator.new_history() workflow_request["history"] = f"hist_id={other_history_id}" - run_workflow_response = self._post(f"workflows/{workflow_id}/invocations", data=workflow_request) + run_workflow_response = self._post(f"workflows/{workflow_id}/invocations", data=workflow_request, json=True) self._assert_status_code_is(run_workflow_response, 403) def test_cannot_run_bootstrap_admin_workflow(self): From 3beb2f9aff4771cbc2c7350b11b8e748779995c9 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Wed, 14 Feb 2024 18:13:59 +0100 Subject: [PATCH 07/57] Regenerate client schema --- client/src/api/schema/schema.ts | 145 ++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 89227e632ec2..18a3db3fa991 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -1828,6 +1828,14 @@ export interface paths { "/api/workflows/{workflow_id}/invocations": { /** Get the list of a user's workflow invocations. */ get: operations["index_invocations_api_workflows__workflow_id__invocations_get"]; + /** + * Schedule the workflow specified by `workflow_id` to run. + * @description .. note:: This method takes the same arguments as + * :func:`galaxy.webapps.galaxy.api.workflows.WorkflowsAPIController.create` above. + * + * :raises: exceptions.MessageException, exceptions.RequestParameterInvalidException + */ + post: operations["Invoke_workflow__api_workflows__workflow_id__invocations_post"]; }; "/api/workflows/{workflow_id}/invocations/{invocation_id}": { /** @@ -7368,6 +7376,104 @@ export interface components { */ action: boolean; }; + /** InvokeWorkflowPayload */ + InvokeWorkflowPayload: { + /** + * Allow Tool State Corrections + * @default false + */ + allow_tool_state_corrections?: boolean | null; + /** + * Batch + * @description If true, the workflow is invoked as a batch. + * @default false + */ + batch?: boolean | null; + /** Ds Map */ + ds_map?: { + [key: string]: Record | undefined; + } | null; + /** Effective Outputs */ + effective_outputs?: boolean | null; + /** + * History + * @description The history to import the workflow into. + */ + history?: string | null; + /** + * History ID + * @description The history to import the workflow into. + */ + history_id?: string | null; + /** + * History Name + * @description The name of the history to import the workflow into. + */ + history_name?: string | null; + /** Inputs */ + inputs?: Record | null; + /** Inputs By */ + inputs_by?: string | null; + /** + * Is Instance + * @description If true, the workflow is invoked as an instance. + * @default false + */ + instance?: boolean | null; + /** + * Legacy + * @default false + */ + legacy?: boolean | null; + /** + * New History Name + * @description The name of the new history to import the workflow into. + */ + new_history_name?: string | null; + /** + * No Add To History + * @default false + */ + no_add_to_history?: boolean | null; + /** Parameters */ + parameters?: Record | null; + /** + * Parameters Normalized + * @default false + */ + parameters_normalized?: boolean | null; + /** Preferred Intermediate Object Store Id */ + preferred_intermediate_object_store_id?: string | null; + /** + * Preferred Object Store ID + * @description The ID of the object store that should be used to store new datasets in this history. + */ + preferred_object_store_id?: string | null; + /** Preferred Outputs Object Store Id */ + preferred_outputs_object_store_id?: string | null; + /** Replacement Params */ + replacement_params?: Record | null; + /** + * Require Exact Tool Versions + * @description If true, exact tool versions are required for workflow invocation. + * @default false + */ + require_exact_tool_versions?: boolean | null; + /** Resource Params */ + resource_params?: Record | null; + /** + * Scheduler + * @description Scheduler to use for workflow invocation. + */ + scheduler?: string | null; + /** Step Parameters */ + step_parameters?: Record | null; + /** + * Use Cached Job + * @default false + */ + use_cached_job?: boolean | null; + }; /** * ItemTagsCreatePayload * @description Payload schema for creating an item tag. @@ -21749,6 +21855,45 @@ export interface operations { }; }; }; + Invoke_workflow__api_workflows__workflow_id__invocations_post: { + /** + * Schedule the workflow specified by `workflow_id` to run. + * @description .. note:: This method takes the same arguments as + * :func:`galaxy.webapps.galaxy.api.workflows.WorkflowsAPIController.create` above. + * + * :raises: exceptions.MessageException, exceptions.RequestParameterInvalidException + */ + parameters: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string | null; + }; + path: { + workflow_id: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["InvokeWorkflowPayload"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": + | components["schemas"]["WorkflowInvocationResponse"] + | components["schemas"]["WorkflowInvocationResponse"][]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; show_workflow_invocation_api_workflows__workflow_id__invocations__invocation_id__get: { /** * Get detailed description of a workflow invocation. From 06505d0607c766983a802c8be426a41d0e9b6982 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Thu, 15 Feb 2024 16:00:09 +0100 Subject: [PATCH 08/57] Add json keyword in tests calling invoke endpoint --- lib/galaxy_test/api/test_workflows.py | 2 +- lib/galaxy_test/base/populators.py | 2 +- test/integration/test_workflow_handler_configuration.py | 2 +- test/integration/test_workflow_scheduling_options.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/galaxy_test/api/test_workflows.py b/lib/galaxy_test/api/test_workflows.py index 02d4135f34f8..7a6589afd2e3 100644 --- a/lib/galaxy_test/api/test_workflows.py +++ b/lib/galaxy_test/api/test_workflows.py @@ -6633,7 +6633,7 @@ def test_parameter_substitution_validation_value_errors_0(self): history=f"hist_id={history_id}", parameters=dumps(dict(validation_repeat={"r2_0|text": ""})) ) url = f"workflows/{workflow_id}/invocations" - invocation_response = self._post(url, data=workflow_request) + invocation_response = self._post(url, data=workflow_request, json=True) # Take a valid stat and make it invalid, assert workflow won't run. self._assert_status_code_is(invocation_response, 400) diff --git a/lib/galaxy_test/base/populators.py b/lib/galaxy_test/base/populators.py index 3bad7b7f034f..d3cf5711f910 100644 --- a/lib/galaxy_test/base/populators.py +++ b/lib/galaxy_test/base/populators.py @@ -1821,7 +1821,7 @@ def validate_invocation_crate_directory(self, crate_directory): def invoke_workflow_raw(self, workflow_id: str, request: dict, assert_ok: bool = False) -> Response: url = f"workflows/{workflow_id}/invocations" - invocation_response = self._post(url, data=request) + invocation_response = self._post(url, data=request, json=True) if assert_ok: invocation_response.raise_for_status() return invocation_response diff --git a/test/integration/test_workflow_handler_configuration.py b/test/integration/test_workflow_handler_configuration.py index 3e2893a8d815..27cfeb5375d8 100644 --- a/test/integration/test_workflow_handler_configuration.py +++ b/test/integration/test_workflow_handler_configuration.py @@ -120,7 +120,7 @@ def _invoke_n_workflows(self, n, history_id: str): request["inputs_by"] = "step_index" url = f"workflows/{workflow_id}/invocations" for _ in range(n): - self._post(url, data=request) + self._post(url, data=request, json=True) def _get_workflow_invocations(self, history_id: str): # Consider exposing handler via the API to reduce breaking diff --git a/test/integration/test_workflow_scheduling_options.py b/test/integration/test_workflow_scheduling_options.py index cdea882fd79d..9ec950e0734c 100644 --- a/test/integration/test_workflow_scheduling_options.py +++ b/test/integration/test_workflow_scheduling_options.py @@ -36,7 +36,7 @@ def test(self): request["inputs"] = dumps(index_map) request["inputs_by"] = "step_index" url = f"workflows/{workflow_id}/invocations" - invocation_response = self._post(url, data=request) + invocation_response = self._post(url, data=request, json=True) invocation_url = url + "/" + invocation_response.json()["id"] time.sleep(5) state = self._get(invocation_url).json()["state"] From 9afa0a22e9483fad708a13b4347f2ea44ef44236 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Thu, 15 Feb 2024 16:56:42 +0100 Subject: [PATCH 09/57] Include more fields in the field validatior to transform them from str to dict --- lib/galaxy/schema/workflows.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/galaxy/schema/workflows.py b/lib/galaxy/schema/workflows.py index 1aa13662d336..d93174313efa 100644 --- a/lib/galaxy/schema/workflows.py +++ b/lib/galaxy/schema/workflows.py @@ -96,27 +96,36 @@ class InvokeWorkflowPayload(GetTargetHistoryPayload): ) allow_tool_state_corrections: Optional[bool] = False use_cached_job: Optional[bool] = False - step_parameters: Optional[Dict[str, Any]] = None + # input_step_parameters: Dict[str, InvocationInputParameter] = Field( # default=..., title="Input step parameters", description="Input step parameters of the workflow invocation." # ) - parameters: Optional[Dict[str, Any]] = None - inputs: Optional[Dict[str, Any]] = None - - @field_validator("ds_map", mode="before", check_fields=False) + @field_validator( + "parameters", + "inputs", + "ds_map", + "resource_params", + "replacement_params", + "step_parameters", + mode="before", + check_fields=False, + ) @classmethod def inputs_string_to_json(cls, v): if isinstance(v, str): return json.loads(v) return v + parameters: Optional[Dict[str, Any]] = None + inputs: Optional[Dict[str, Any]] = None ds_map: Optional[Dict[str, Dict[str, Any]]] = None + resource_params: Optional[Dict[str, Any]] = None + replacement_params: Optional[Dict[str, Any]] = None + step_parameters: Optional[Dict[str, Any]] = None no_add_to_history: Optional[bool] = False legacy: Optional[bool] = False parameters_normalized: Optional[bool] = False inputs_by: Optional[str] = None - resource_params: Optional[Dict[str, Any]] = None - replacement_params: Optional[Dict[str, Any]] = None effective_outputs: Optional[bool] = None preferred_object_store_id: Optional[str] = PreferredObjectStoreIdField preferred_intermediate_object_store_id: Optional[str] = None From 87b6bed37122f3a2e7856117356eab416951b775 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Fri, 16 Feb 2024 10:59:21 +0100 Subject: [PATCH 10/57] Re-add mapping to some of the legacy routes of the Workflows API --- lib/galaxy/webapps/galaxy/buildapp.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py index b561198aa400..aa17e4db1ea3 100644 --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -690,6 +690,19 @@ def populate_api_routes(webapp, app): # conditions=dict(method=["POST"]), # ) + invoke_names = { + "invocations": "", + "usage": "_deprecated", + } + for noun, suffix in invoke_names.items(): + name = f"{noun}{suffix}" + webapp.mapper.connect( + f"workflow_{name}", + "/api/workflows/{workflow_id}/%s" % noun, + controller="workflows", + action="invoke", + conditions=dict(method=["POST"]), + ) # ================================ # ===== USERS API ===== # ================================ From 3f9d8f64c5a8a45ced95143695285f4bace4bfa4 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Fri, 16 Feb 2024 11:00:05 +0100 Subject: [PATCH 11/57] Remove legacy mapping to the invocation routes --- lib/galaxy/webapps/galaxy/buildapp.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py index aa17e4db1ea3..21cad6aa8ea7 100644 --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -691,7 +691,6 @@ def populate_api_routes(webapp, app): # ) invoke_names = { - "invocations": "", "usage": "_deprecated", } for noun, suffix in invoke_names.items(): From 28aaac65c9ce57ce3d80cb88fa1f3667ce2c182d Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Fri, 16 Feb 2024 11:00:22 +0100 Subject: [PATCH 12/57] Refine pydantic model of the show operation --- lib/galaxy/schema/workflows.py | 124 +++++++++++++++++++++++++-------- 1 file changed, 94 insertions(+), 30 deletions(-) diff --git a/lib/galaxy/schema/workflows.py b/lib/galaxy/schema/workflows.py index d93174313efa..f96474a50c64 100644 --- a/lib/galaxy/schema/workflows.py +++ b/lib/galaxy/schema/workflows.py @@ -47,59 +47,71 @@ # description="Name of the workflow to create when extracting a workflow from history", # ) -# TODO - add description to fields - class GetTargetHistoryPayload(Model): + # TODO - add description to fields history: Optional[str] = Field( None, title="History", - description="The history to import the workflow into.", + # description="The history to import the workflow into.", + description="TODO", ) history_id: Optional[str] = Field( None, title="History ID", - description="The history to import the workflow into.", + # description="The history to import the workflow into.", + description="TODO", ) history_name: Optional[str] = Field( None, title="History Name", - description="The name of the history to import the workflow into.", + # description="The name of the history to import the workflow into.", + description="TODO", ) new_history_name: Optional[str] = Field( None, title="New History Name", - description="The name of the new history to import the workflow into.", + # description="The name of the new history to import the workflow into.", + description="TODO", ) class InvokeWorkflowPayload(GetTargetHistoryPayload): + # TODO - add description to fields instance: Optional[bool] = Field( False, - title="Is Instance", - description="If true, the workflow is invoked as an instance.", + title="Is instance", + description="TODO", ) scheduler: Optional[str] = Field( None, title="Scheduler", - description="Scheduler to use for workflow invocation.", + # description="Scheduler to use for workflow invocation.", + description="TODO", ) batch: Optional[bool] = Field( False, title="Batch", - description="If true, the workflow is invoked as a batch.", + # description="If true, the workflow is invoked as a batch.", + description="TODO", ) require_exact_tool_versions: Optional[bool] = Field( - False, + True, title="Require Exact Tool Versions", - description="If true, exact tool versions are required for workflow invocation.", + # description="If true, exact tool versions are required for workflow invocation.", + description="TODO", + ) + allow_tool_state_corrections: Optional[bool] = Field( + False, + title="Allow tool state corrections", + description="TODO", + ) + use_cached_job: Optional[bool] = Field( + False, + title="Use cached job", + description="TODO", ) - allow_tool_state_corrections: Optional[bool] = False - use_cached_job: Optional[bool] = False - # input_step_parameters: Dict[str, InvocationInputParameter] = Field( - # default=..., title="Input step parameters", description="Input step parameters of the workflow invocation." - # ) @field_validator( "parameters", "inputs", @@ -116,17 +128,69 @@ def inputs_string_to_json(cls, v): return json.loads(v) return v - parameters: Optional[Dict[str, Any]] = None - inputs: Optional[Dict[str, Any]] = None - ds_map: Optional[Dict[str, Dict[str, Any]]] = None - resource_params: Optional[Dict[str, Any]] = None - replacement_params: Optional[Dict[str, Any]] = None - step_parameters: Optional[Dict[str, Any]] = None - no_add_to_history: Optional[bool] = False - legacy: Optional[bool] = False - parameters_normalized: Optional[bool] = False - inputs_by: Optional[str] = None - effective_outputs: Optional[bool] = None + parameters: Optional[Dict[str, Any]] = Field( + {}, + title="Parameters", + description="TODO", + ) + inputs: Optional[Dict[str, Any]] = Field( + None, + title="Inputs", + description="TODO", + ) + ds_map: Optional[Dict[str, Dict[str, Any]]] = Field( + {}, + title="Dataset Map", + description="TODO", + ) + resource_params: Optional[Dict[str, Any]] = Field( + {}, + title="Resource Parameters", + description="TODO", + ) + replacement_params: Optional[Dict[str, Any]] = Field( + {}, + title="Replacement Parameters", + description="TODO", + ) + step_parameters: Optional[Dict[str, Any]] = Field( + None, + title="Step Parameters", + description="TODO", + ) + no_add_to_history: Optional[bool] = Field( + False, + title="No Add to History", + description="TODO", + ) + legacy: Optional[bool] = Field( + False, + title="Legacy", + description="TODO", + ) + parameters_normalized: Optional[bool] = Field( + False, + title="Parameters Normalized", + description="TODO", + ) + inputs_by: Optional[str] = Field( + None, + title="Inputs By", + description="TODO", + ) + effective_outputs: Optional[bool] = Field( + None, + title="Effective Outputs", + description="TODO", + ) + preferred_intermediate_object_store_id: Optional[str] = Field( + None, + title="Preferred Intermediate Object Store ID", + description="TODO", + ) + preferred_outputs_object_store_id: Optional[str] = Field( + None, + title="Preferred Outputs Object Store ID", + description="TODO", + ) preferred_object_store_id: Optional[str] = PreferredObjectStoreIdField - preferred_intermediate_object_store_id: Optional[str] = None - preferred_outputs_object_store_id: Optional[str] = None From 22a260b10761dada8a960cf71c87799dd812babe Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Fri, 16 Feb 2024 11:00:36 +0100 Subject: [PATCH 13/57] Make use of the pydantic model of the show operation in the service layer --- lib/galaxy/webapps/galaxy/api/workflows.py | 2 +- .../webapps/galaxy/services/workflows.py | 26 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 643fd20b6efd..d7f75aa0098e 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -1081,7 +1081,7 @@ def invoke( :raises: exceptions.MessageException, exceptions.RequestParameterInvalidException """ - return self.service.invoke_workflow(trans, workflow_id, payload.model_dump(exclude_unset=True)) + return self.service.invoke_workflow(trans, workflow_id, payload) @router.get( "/api/workflows/{workflow_id}/versions", diff --git a/lib/galaxy/webapps/galaxy/services/workflows.py b/lib/galaxy/webapps/galaxy/services/workflows.py index e5cdbb19dabe..c8999264c953 100644 --- a/lib/galaxy/webapps/galaxy/services/workflows.py +++ b/lib/galaxy/webapps/galaxy/services/workflows.py @@ -10,10 +10,7 @@ from fastapi import HTTPException -from galaxy import ( - util, - web, -) +from galaxy import web from galaxy.managers.context import ProvidesUserContext from galaxy.managers.notification import NotificationManager from galaxy.managers.workflows import ( @@ -28,6 +25,7 @@ InvocationsStateCounts, WorkflowIndexQueryPayload, ) +from galaxy.schema.workflows import InvokeWorkflowPayload from galaxy.util.tool_shed.tool_shed_registry import Registry from galaxy.webapps.galaxy.services.base import ServiceBase from galaxy.webapps.galaxy.services.sharable import ShareableService @@ -116,19 +114,24 @@ def index( return rval, total_matches def invoke_workflow( - self, trans, workflow_id, payload + self, + trans, + workflow_id, + payload: InvokeWorkflowPayload, ) -> Union[WorkflowInvocationResponse, List[WorkflowInvocationResponse]]: - # TODO - make use of pydantic model for payload # Get workflow + accessibility check. - by_stored_id = not payload.get("instance", False) + # by_stored_id = not payload.get("instance", False) + by_stored_id = not payload.instance stored_workflow = self._workflows_manager.get_stored_accessible_workflow(trans, workflow_id, by_stored_id) workflow = stored_workflow.latest_workflow - run_configs = build_workflow_run_configs(trans, workflow, payload) - is_batch = payload.get("batch") + run_configs = build_workflow_run_configs(trans, workflow, payload.model_dump(exclude_unset=True)) + is_batch = payload.batch + # is_batch = payload.get("batch") if not is_batch and len(run_configs) != 1: raise HTTPException(status_code=400, detail="Must specify 'batch' to use batch parameters.") - require_exact_tool_versions = util.string_as_bool(payload.get("require_exact_tool_versions", "true")) + require_exact_tool_versions = payload.require_exact_tool_versions + # require_exact_tool_versions = util.string_as_bool(payload.get("require_exact_tool_versions", "true")) tools = self._workflow_contents_manager.get_all_tools(workflow) missing_tools = [ tool @@ -158,7 +161,8 @@ def invoke_workflow( invocations = [] for run_config in run_configs: - workflow_scheduler_id = payload.get("scheduler", None) + workflow_scheduler_id = payload.scheduler + # workflow_scheduler_id = payload.get("scheduler", None) # TODO: workflow scheduler hints work_request_params = dict(scheduler=workflow_scheduler_id) workflow_invocation = queue_invoke( From f9c847f9139c2d0e69f27b12f728cd435296b16b Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Fri, 16 Feb 2024 11:03:22 +0100 Subject: [PATCH 14/57] Remove InvocationService from the WorkflowsAPIController as it is not used anymore --- lib/galaxy/webapps/galaxy/api/workflows.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index d7f75aa0098e..daf190896c96 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -135,7 +135,6 @@ class WorkflowsAPIController( ConsumesModelStores, ): service: WorkflowsService = depends(WorkflowsService) - invocations_service: InvocationsService = depends(InvocationsService) def __init__(self, app: StructuredApp): super().__init__(app) From d6bff713cb7c2bbbbb4dc4123584d2fae47c6481 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Fri, 16 Feb 2024 11:04:44 +0100 Subject: [PATCH 15/57] Regenerate the client schema --- client/src/api/schema/schema.ts | 85 ++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 23 deletions(-) diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 18a3db3fa991..e23f1f510152 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -7379,97 +7379,136 @@ export interface components { /** InvokeWorkflowPayload */ InvokeWorkflowPayload: { /** - * Allow Tool State Corrections + * Allow tool state corrections + * @description TODO * @default false */ allow_tool_state_corrections?: boolean | null; /** * Batch - * @description If true, the workflow is invoked as a batch. + * @description TODO * @default false */ batch?: boolean | null; - /** Ds Map */ + /** + * Dataset Map + * @description TODO + * @default {} + */ ds_map?: { [key: string]: Record | undefined; } | null; - /** Effective Outputs */ + /** + * Effective Outputs + * @description TODO + */ effective_outputs?: boolean | null; /** * History - * @description The history to import the workflow into. + * @description TODO */ history?: string | null; /** * History ID - * @description The history to import the workflow into. + * @description TODO */ history_id?: string | null; /** * History Name - * @description The name of the history to import the workflow into. + * @description TODO */ history_name?: string | null; - /** Inputs */ + /** + * Inputs + * @description TODO + */ inputs?: Record | null; - /** Inputs By */ + /** + * Inputs By + * @description TODO + */ inputs_by?: string | null; /** - * Is Instance - * @description If true, the workflow is invoked as an instance. + * Is instance + * @description TODO * @default false */ instance?: boolean | null; /** * Legacy + * @description TODO * @default false */ legacy?: boolean | null; /** * New History Name - * @description The name of the new history to import the workflow into. + * @description TODO */ new_history_name?: string | null; /** - * No Add To History + * No Add to History + * @description TODO * @default false */ no_add_to_history?: boolean | null; - /** Parameters */ + /** + * Parameters + * @description TODO + * @default {} + */ parameters?: Record | null; /** * Parameters Normalized + * @description TODO * @default false */ parameters_normalized?: boolean | null; - /** Preferred Intermediate Object Store Id */ + /** + * Preferred Intermediate Object Store ID + * @description TODO + */ preferred_intermediate_object_store_id?: string | null; /** * Preferred Object Store ID * @description The ID of the object store that should be used to store new datasets in this history. */ preferred_object_store_id?: string | null; - /** Preferred Outputs Object Store Id */ + /** + * Preferred Outputs Object Store ID + * @description TODO + */ preferred_outputs_object_store_id?: string | null; - /** Replacement Params */ + /** + * Replacement Parameters + * @description TODO + * @default {} + */ replacement_params?: Record | null; /** * Require Exact Tool Versions - * @description If true, exact tool versions are required for workflow invocation. - * @default false + * @description TODO + * @default true */ require_exact_tool_versions?: boolean | null; - /** Resource Params */ + /** + * Resource Parameters + * @description TODO + * @default {} + */ resource_params?: Record | null; /** * Scheduler - * @description Scheduler to use for workflow invocation. + * @description TODO */ scheduler?: string | null; - /** Step Parameters */ + /** + * Step Parameters + * @description TODO + */ step_parameters?: Record | null; /** - * Use Cached Job + * Use cached job + * @description TODO * @default false */ use_cached_job?: boolean | null; From dcd2167ce739c9b7698bef5196df5e891fefad5b Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Fri, 16 Feb 2024 14:37:32 +0100 Subject: [PATCH 16/57] Add mising mappings to endpoints of the WorkflowAPI --- lib/galaxy/webapps/galaxy/api/workflows.py | 8 +++++- lib/galaxy/webapps/galaxy/buildapp.py | 31 +++++++++++++--------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index daf190896c96..894b8f9bf4e4 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -1064,9 +1064,15 @@ def undelete_workflow( @router.post( "/api/workflows/{workflow_id}/invocations", - name="Invoke workflow.", + name="Invoke workflow", summary="Schedule the workflow specified by `workflow_id` to run.", ) + @router.post( + "/api/workflows/{workflow_id}/usage", + name="Invoke workflow", + summary="Schedule the workflow specified by `workflow_id` to run.", + deprecated=True, + ) def invoke( self, # workflow_id: StoredWorkflowIDPathParam, diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py index 21cad6aa8ea7..5b8d1a84027a 100644 --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -689,19 +689,24 @@ def populate_api_routes(webapp, app): # action="import_tool_version", # conditions=dict(method=["POST"]), # ) - - invoke_names = { - "usage": "_deprecated", - } - for noun, suffix in invoke_names.items(): - name = f"{noun}{suffix}" - webapp.mapper.connect( - f"workflow_{name}", - "/api/workflows/{workflow_id}/%s" % noun, - controller="workflows", - action="invoke", - conditions=dict(method=["POST"]), - ) + webapp.mapper.connect( + "/api/workflows/{encoded_workflow_id}", + controller="workflows", + action="show", + conditions=dict(method=["GET"]), + ) + webapp.mapper.connect( + "/api/workflows/{encoded_workflow_id}", + controller="workflows", + action="update", + conditions=dict(method=["PUT"]), + ) + webapp.mapper.connect( + "/api/workflows", + controller="workflows", + action="create", + conditions=dict(method=["POST"]), + ) # ================================ # ===== USERS API ===== # ================================ From 8bbfc77f08a87f1f82da92ee6eced554cd187190 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Fri, 16 Feb 2024 16:26:58 +0100 Subject: [PATCH 17/57] Add json keyword to tests, when posting to invoke operation of Workflow API --- lib/galaxy_test/api/test_workflows.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy_test/api/test_workflows.py b/lib/galaxy_test/api/test_workflows.py index 7a6589afd2e3..f1e1c9fe4e9c 100644 --- a/lib/galaxy_test/api/test_workflows.py +++ b/lib/galaxy_test/api/test_workflows.py @@ -6538,7 +6538,7 @@ def test_run_batch(self): "parameters_normalized": True, "parameters": dumps(parameters), } - invocation_response = self._post(f"workflows/{workflow_id}/usage", data=workflow_request) + invocation_response = self._post(f"workflows/{workflow_id}/usage", data=workflow_request, json=True) self._assert_status_code_is(invocation_response, 200) time.sleep(5) self.dataset_populator.wait_for_history(history_id, assert_ok=True) @@ -6590,7 +6590,7 @@ def test_run_batch_inputs(self): "parameters_normalized": True, "parameters": dumps(parameters), } - invocation_response = self._post(f"workflows/{workflow_id}/usage", data=workflow_request) + invocation_response = self._post(f"workflows/{workflow_id}/usage", data=workflow_request, json=True) self._assert_status_code_is(invocation_response, 200) time.sleep(5) self.dataset_populator.wait_for_history(history_id, assert_ok=True) From a240e38a1153a5b42b6e22e6ffb0a2b6099b9dae Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Fri, 16 Feb 2024 16:47:07 +0100 Subject: [PATCH 18/57] Access error message of API response correctly --- test/integration/test_workflow_invocation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/test_workflow_invocation.py b/test/integration/test_workflow_invocation.py index 72276bf77979..70d0d541e83b 100644 --- a/test/integration/test_workflow_invocation.py +++ b/test/integration/test_workflow_invocation.py @@ -110,7 +110,7 @@ def test_run_workflow_with_missing_tool(self): ) self._assert_status_code_is(invocation_response, 400) assert ( - invocation_response.json().get("err_msg") + invocation_response.json().get("detail") == "Workflow was not invoked; the following required tools are not installed: nonexistent_tool (version 0.1), compose_text_param (version 0.0.1)" ) # should fail but return only the tool_id of non_existent tool as another version of compose_text_param is installed @@ -119,6 +119,6 @@ def test_run_workflow_with_missing_tool(self): ) self._assert_status_code_is(invocation_response, 400) assert ( - invocation_response.json().get("err_msg") + invocation_response.json().get("detail") == "Workflow was not invoked; the following required tools are not installed: nonexistent_tool" ) From 364ec7a7f819e068c3d141336f327b6e2aca8aaf Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Fri, 16 Feb 2024 16:47:37 +0100 Subject: [PATCH 19/57] Rgenerate the client schema --- client/src/api/schema/schema.ts | 53 +++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index e23f1f510152..e457503a5636 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -1835,7 +1835,7 @@ export interface paths { * * :raises: exceptions.MessageException, exceptions.RequestParameterInvalidException */ - post: operations["Invoke_workflow__api_workflows__workflow_id__invocations_post"]; + post: operations["Invoke_workflow_api_workflows__workflow_id__invocations_post"]; }; "/api/workflows/{workflow_id}/invocations/{invocation_id}": { /** @@ -1948,6 +1948,15 @@ export interface paths { * @deprecated */ get: operations["index_invocations_api_workflows__workflow_id__usage_get"]; + /** + * Schedule the workflow specified by `workflow_id` to run. + * @deprecated + * @description .. note:: This method takes the same arguments as + * :func:`galaxy.webapps.galaxy.api.workflows.WorkflowsAPIController.create` above. + * + * :raises: exceptions.MessageException, exceptions.RequestParameterInvalidException + */ + post: operations["Invoke_workflow_api_workflows__workflow_id__usage_post"]; }; "/api/workflows/{workflow_id}/usage/{invocation_id}": { /** @@ -21894,7 +21903,7 @@ export interface operations { }; }; }; - Invoke_workflow__api_workflows__workflow_id__invocations_post: { + Invoke_workflow_api_workflows__workflow_id__invocations_post: { /** * Schedule the workflow specified by `workflow_id` to run. * @description .. note:: This method takes the same arguments as @@ -22601,6 +22610,46 @@ export interface operations { }; }; }; + Invoke_workflow_api_workflows__workflow_id__usage_post: { + /** + * Schedule the workflow specified by `workflow_id` to run. + * @deprecated + * @description .. note:: This method takes the same arguments as + * :func:`galaxy.webapps.galaxy.api.workflows.WorkflowsAPIController.create` above. + * + * :raises: exceptions.MessageException, exceptions.RequestParameterInvalidException + */ + parameters: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string | null; + }; + path: { + workflow_id: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["InvokeWorkflowPayload"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": + | components["schemas"]["WorkflowInvocationResponse"] + | components["schemas"]["WorkflowInvocationResponse"][]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; show_workflow_invocation_api_workflows__workflow_id__usage__invocation_id__get: { /** * Get detailed description of a workflow invocation. From f319eb13438dce9ac7ae09a615fe83ee5334a3b6 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Sat, 17 Feb 2024 19:02:27 +0100 Subject: [PATCH 20/57] Add description to fields of pydantic model --- lib/galaxy/schema/workflows.py | 58 +++++++++++++++------------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/lib/galaxy/schema/workflows.py b/lib/galaxy/schema/workflows.py index f96474a50c64..ca2f9a7b54e3 100644 --- a/lib/galaxy/schema/workflows.py +++ b/lib/galaxy/schema/workflows.py @@ -49,35 +49,29 @@ class GetTargetHistoryPayload(Model): - # TODO - add description to fields + # TODO - Are the descriptions correct? history: Optional[str] = Field( None, title="History", - # description="The history to import the workflow into.", - description="TODO", + # description="The encoded history id - passed exactly like this 'hist_id=...' - to import the workflow into. Or the name of the new history to import the workflow into.", + description="The encoded history id - passed exactly like this 'hist_id=...' - into which to import. Or the name of the new history into which to import.", ) history_id: Optional[str] = Field( None, title="History ID", # description="The history to import the workflow into.", - description="TODO", - ) - history_name: Optional[str] = Field( - None, - title="History Name", - # description="The name of the history to import the workflow into.", - description="TODO", + description="The encoded history id into which to import.", ) new_history_name: Optional[str] = Field( None, title="New History Name", # description="The name of the new history to import the workflow into.", - description="TODO", + description="The name of the new history into which to import.", ) class InvokeWorkflowPayload(GetTargetHistoryPayload): - # TODO - add description to fields + # TODO - Are the descriptions correct? instance: Optional[bool] = Field( False, title="Is instance", @@ -86,30 +80,33 @@ class InvokeWorkflowPayload(GetTargetHistoryPayload): scheduler: Optional[str] = Field( None, title="Scheduler", - # description="Scheduler to use for workflow invocation.", - description="TODO", + description="Scheduler to use for workflow invocation.", ) batch: Optional[bool] = Field( False, title="Batch", - # description="If true, the workflow is invoked as a batch.", - description="TODO", + description="Indicates if the workflow is invoked as a batch.", ) require_exact_tool_versions: Optional[bool] = Field( True, title="Require Exact Tool Versions", - # description="If true, exact tool versions are required for workflow invocation.", - description="TODO", + description="If true, exact tool versions are required for workflow invocation.", + # description="TODO", ) allow_tool_state_corrections: Optional[bool] = Field( False, title="Allow tool state corrections", - description="TODO", + description="Indicates if tool state corrections are allowed for workflow invocation.", ) use_cached_job: Optional[bool] = Field( False, title="Use cached job", - description="TODO", + description="Indicated whether to use a cached job for workflow invocation.", + ) + parameters_normalized: Optional[bool] = Field( + False, + title="Parameters Normalized", + description="Indicates if parameters are already normalized for workflow invocation.", ) @field_validator( @@ -131,7 +128,7 @@ def inputs_string_to_json(cls, v): parameters: Optional[Dict[str, Any]] = Field( {}, title="Parameters", - description="TODO", + description="The raw parameters for the workflow invocation.", ) inputs: Optional[Dict[str, Any]] = Field( None, @@ -161,36 +158,33 @@ def inputs_string_to_json(cls, v): no_add_to_history: Optional[bool] = Field( False, title="No Add to History", - description="TODO", + description="Indicates if the workflow invocation should not be added to the history.", ) legacy: Optional[bool] = Field( False, title="Legacy", - description="TODO", - ) - parameters_normalized: Optional[bool] = Field( - False, - title="Parameters Normalized", - description="TODO", + description="Indicating if to use legacy workflow invocation.", ) inputs_by: Optional[str] = Field( None, title="Inputs By", - description="TODO", + # lib/galaxy/workflow/run_request.py - see line 60 + description="How inputs maps to inputs (datasets/collections) to workflows steps.", ) - effective_outputs: Optional[bool] = Field( + effective_outputs: Optional[Any] = Field( None, title="Effective Outputs", + # lib/galaxy/workflow/run_request.py - see line 455 description="TODO", ) preferred_intermediate_object_store_id: Optional[str] = Field( None, title="Preferred Intermediate Object Store ID", - description="TODO", + description="The ID of the ? object store that should be used to store ? datasets in this history.", ) preferred_outputs_object_store_id: Optional[str] = Field( None, title="Preferred Outputs Object Store ID", - description="TODO", + description="The ID of the object store that should be used to store ? datasets in this history.", ) preferred_object_store_id: Optional[str] = PreferredObjectStoreIdField From 4aa2f74470f30e70ec17efea1521c66bc763261b Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Sat, 17 Feb 2024 19:03:15 +0100 Subject: [PATCH 21/57] Type workflow id path parameter of invoke operation --- lib/galaxy/webapps/galaxy/api/workflows.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 894b8f9bf4e4..7210ab0a638d 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -23,6 +23,10 @@ ) from gxformat2._yaml import ordered_dump from markupsafe import escape +from pydantic import ( + UUID1, + UUID4, +) from starlette.responses import StreamingResponse from typing_extensions import Annotated @@ -821,6 +825,15 @@ def __get_stored_workflow(self, trans, workflow_id, **kwd): ), ] +MultiTypeWorkflowIDPathParam = Annotated[ + Union[UUID4, UUID1, DecodedDatabaseIdField], + Path( + ..., + title="Workflow ID", + description="The database identifier - UUID or encoded - of the Workflow..", + ), +] + DeletedQueryParam: bool = Query( default=False, title="Display deleted", description="Whether to restrict result to deleted workflows." ) @@ -1075,9 +1088,9 @@ def undelete_workflow( ) def invoke( self, - # workflow_id: StoredWorkflowIDPathParam, payload: InvokeWorkflowBody, - workflow_id: str = Path(...), + # workflow_id: str = Path(...), + workflow_id: MultiTypeWorkflowIDPathParam, trans: ProvidesHistoryContext = DependsOnTrans, ) -> Union[WorkflowInvocationResponse, List[WorkflowInvocationResponse]]: """ From a8b35eb1e90dca9b39cd67ae42311c3c182b6056 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Sun, 18 Feb 2024 10:44:45 +0100 Subject: [PATCH 22/57] Regenerate the client schema --- client/src/api/schema/schema.ts | 43 +++++++++++++++------------------ 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index e457503a5636..805bee90084b 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -7389,13 +7389,13 @@ export interface components { InvokeWorkflowPayload: { /** * Allow tool state corrections - * @description TODO + * @description Indicates if tool state corrections are allowed for workflow invocation. * @default false */ allow_tool_state_corrections?: boolean | null; /** * Batch - * @description TODO + * @description Indicates if the workflow is invoked as a batch. * @default false */ batch?: boolean | null; @@ -7411,22 +7411,17 @@ export interface components { * Effective Outputs * @description TODO */ - effective_outputs?: boolean | null; + effective_outputs?: Record | null; /** * History - * @description TODO + * @description The encoded history id - passed exactly like this 'hist_id=...' - into which to import. Or the name of the new history into which to import. */ history?: string | null; /** * History ID - * @description TODO + * @description The encoded history id into which to import. */ history_id?: string | null; - /** - * History Name - * @description TODO - */ - history_name?: string | null; /** * Inputs * @description TODO @@ -7434,7 +7429,7 @@ export interface components { inputs?: Record | null; /** * Inputs By - * @description TODO + * @description How inputs maps to inputs (datasets/collections) to workflows steps. */ inputs_by?: string | null; /** @@ -7445,36 +7440,36 @@ export interface components { instance?: boolean | null; /** * Legacy - * @description TODO + * @description Indicating if to use legacy workflow invocation. * @default false */ legacy?: boolean | null; /** * New History Name - * @description TODO + * @description The name of the new history into which to import. */ new_history_name?: string | null; /** * No Add to History - * @description TODO + * @description Indicates if the workflow invocation should not be added to the history. * @default false */ no_add_to_history?: boolean | null; /** * Parameters - * @description TODO + * @description The raw parameters for the workflow invocation. * @default {} */ parameters?: Record | null; /** * Parameters Normalized - * @description TODO + * @description Indicates if parameters are already normalized for workflow invocation. * @default false */ parameters_normalized?: boolean | null; /** * Preferred Intermediate Object Store ID - * @description TODO + * @description The ID of the ? object store that should be used to store ? datasets in this history. */ preferred_intermediate_object_store_id?: string | null; /** @@ -7484,7 +7479,7 @@ export interface components { preferred_object_store_id?: string | null; /** * Preferred Outputs Object Store ID - * @description TODO + * @description The ID of the object store that should be used to store ? datasets in this history. */ preferred_outputs_object_store_id?: string | null; /** @@ -7495,7 +7490,7 @@ export interface components { replacement_params?: Record | null; /** * Require Exact Tool Versions - * @description TODO + * @description If true, exact tool versions are required for workflow invocation. * @default true */ require_exact_tool_versions?: boolean | null; @@ -7507,7 +7502,7 @@ export interface components { resource_params?: Record | null; /** * Scheduler - * @description TODO + * @description Scheduler to use for workflow invocation. */ scheduler?: string | null; /** @@ -7517,7 +7512,7 @@ export interface components { step_parameters?: Record | null; /** * Use cached job - * @description TODO + * @description Indicated whether to use a cached job for workflow invocation. * @default false */ use_cached_job?: boolean | null; @@ -21916,8 +21911,9 @@ export interface operations { header?: { "run-as"?: string | null; }; + /** @description The database identifier - UUID or encoded - of the Workflow.. */ path: { - workflow_id: string; + workflow_id: string | string | string; }; }; requestBody: { @@ -22624,8 +22620,9 @@ export interface operations { header?: { "run-as"?: string | null; }; + /** @description The database identifier - UUID or encoded - of the Workflow.. */ path: { - workflow_id: string; + workflow_id: string | string | string; }; }; requestBody: { From 3a31dcdaac5be228ad9570c1ff4c2f97536ef763 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Sun, 18 Feb 2024 18:11:04 +0100 Subject: [PATCH 23/57] Add description to fields of pydantic model --- lib/galaxy/schema/workflows.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/schema/workflows.py b/lib/galaxy/schema/workflows.py index ca2f9a7b54e3..7980fdd19d6a 100644 --- a/lib/galaxy/schema/workflows.py +++ b/lib/galaxy/schema/workflows.py @@ -75,7 +75,7 @@ class InvokeWorkflowPayload(GetTargetHistoryPayload): instance: Optional[bool] = Field( False, title="Is instance", - description="TODO", + description="True when fetching by Workflow ID, False when fetching by StoredWorkflow ID", ) scheduler: Optional[str] = Field( None, From 6e71cee492303ecea7b63935de473ef819b61e5a Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Sun, 18 Feb 2024 18:25:10 +0100 Subject: [PATCH 24/57] Refactor the refactor operation of the WorkflowAPI --- lib/galaxy/webapps/galaxy/api/workflows.py | 48 +++++++++++++--------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 7210ab0a638d..b2f46515c3fc 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -46,7 +46,10 @@ from galaxy.managers.workflows import ( MissingToolsException, RefactorRequest, + RefactorResponse, + WorkflowContentsManager, WorkflowCreateOptions, + WorkflowsManager, WorkflowUpdateOptions, ) from galaxy.model.base import transaction @@ -558,26 +561,6 @@ def update(self, trans: GalaxyWebTransaction, id, payload, **kwds): raise exceptions.RequestParameterInvalidException(message) return self.workflow_contents_manager.workflow_to_dict(trans, stored_workflow, style="instance") - @expose_api - def refactor(self, trans, id, payload, **kwds): - """ - * PUT /api/workflows/{id}/refactor - updates the workflow stored with ``id`` - - :type id: str - :param id: the encoded id of the workflow to update - :param instance: true if fetch by Workflow ID instead of StoredWorkflow id, false - by default. - :type instance: boolean - :type payload: dict - :param payload: a dictionary containing list of actions to apply. - :rtype: dict - :returns: serialized version of the workflow - """ - stored_workflow = self.__get_stored_workflow(trans, id, **kwds) - refactor_request = RefactorRequest(**payload) - return self.workflow_contents_manager.refactor(trans, stored_workflow, refactor_request) - @expose_api def build_module(self, trans: GalaxyWebTransaction, payload=None): """ @@ -919,10 +902,21 @@ def __get_stored_workflow(self, trans, workflow_id, **kwd): ), ] +RefactorWorkflowBody = Annotated[ + RefactorRequest, + Body( + default=..., + title="Refactor workflow", + description="The values to refactor a workflow.", + ), +] + @router.cbv class FastAPIWorkflows: service: WorkflowsService = depends(WorkflowsService) + manager: WorkflowsManager = depends(WorkflowsManager) + contents_manager: WorkflowContentsManager = depends(WorkflowContentsManager) @router.get( "/api/workflows", @@ -999,6 +993,20 @@ def disable_link_access( """Makes this item inaccessible by a URL link and return the current sharing status.""" return self.service.shareable_service.disable_link_access(trans, workflow_id) + @router.put( + "/api/workflows/{workflow_id}/refactor", + summary="Updates the workflow stored with the given ID.", + ) + def refactor( + self, + workflow_id: StoredWorkflowIDPathParam, + payload: RefactorWorkflowBody, + instance: InstanceQueryParam = False, + trans: ProvidesUserContext = DependsOnTrans, + ) -> RefactorResponse: + stored_workflow = self.manager.get_stored_workflow(trans, workflow_id, by_stored_id=not instance) + return self.contents_manager.refactor(trans, stored_workflow, payload) + @router.put( "/api/workflows/{workflow_id}/publish", summary="Makes this item public and accessible by a URL link.", From c4d6031fe8bb4b1fe7d9eeee5c978915d24c0012 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Sun, 18 Feb 2024 18:26:40 +0100 Subject: [PATCH 25/57] Remove the mapping to the legacy route --- lib/galaxy/webapps/galaxy/buildapp.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py index 5b8d1a84027a..0430f96d0d3c 100644 --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -593,9 +593,6 @@ def populate_api_routes(webapp, app): webapp.mapper.connect( "/api/workflows/menu", action="set_workflow_menu", controller="workflows", conditions=dict(method=["PUT"]) ) - webapp.mapper.connect( - "/api/workflows/{id}/refactor", action="refactor", controller="workflows", conditions=dict(method=["PUT"]) - ) webapp.mapper.resource("workflow", "workflows", path_prefix="/api") # ---- visualizations registry ---- generic template renderer From 36751dcac65f9d410cccaa4125df72b23966cf97 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Sun, 18 Feb 2024 18:28:29 +0100 Subject: [PATCH 26/57] Remove comments --- lib/galaxy/webapps/galaxy/api/workflows.py | 9 --------- lib/galaxy/webapps/galaxy/services/workflows.py | 4 ---- 2 files changed, 13 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index b2f46515c3fc..f5d506599cf8 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -81,8 +81,6 @@ WorkflowSortByEnum, ) from galaxy.schema.workflows import InvokeWorkflowPayload - -# from galaxy.schema.workflows import InvokeWorkflowPayload from galaxy.structured_app import StructuredApp from galaxy.tool_shed.galaxy_install.install_manager import InstallRepositoryManager from galaxy.tools import recommendations @@ -1097,16 +1095,9 @@ def undelete_workflow( def invoke( self, payload: InvokeWorkflowBody, - # workflow_id: str = Path(...), workflow_id: MultiTypeWorkflowIDPathParam, trans: ProvidesHistoryContext = DependsOnTrans, ) -> Union[WorkflowInvocationResponse, List[WorkflowInvocationResponse]]: - """ - .. note:: This method takes the same arguments as - :func:`galaxy.webapps.galaxy.api.workflows.WorkflowsAPIController.create` above. - - :raises: exceptions.MessageException, exceptions.RequestParameterInvalidException - """ return self.service.invoke_workflow(trans, workflow_id, payload) @router.get( diff --git a/lib/galaxy/webapps/galaxy/services/workflows.py b/lib/galaxy/webapps/galaxy/services/workflows.py index c8999264c953..798bda20a576 100644 --- a/lib/galaxy/webapps/galaxy/services/workflows.py +++ b/lib/galaxy/webapps/galaxy/services/workflows.py @@ -120,18 +120,15 @@ def invoke_workflow( payload: InvokeWorkflowPayload, ) -> Union[WorkflowInvocationResponse, List[WorkflowInvocationResponse]]: # Get workflow + accessibility check. - # by_stored_id = not payload.get("instance", False) by_stored_id = not payload.instance stored_workflow = self._workflows_manager.get_stored_accessible_workflow(trans, workflow_id, by_stored_id) workflow = stored_workflow.latest_workflow run_configs = build_workflow_run_configs(trans, workflow, payload.model_dump(exclude_unset=True)) is_batch = payload.batch - # is_batch = payload.get("batch") if not is_batch and len(run_configs) != 1: raise HTTPException(status_code=400, detail="Must specify 'batch' to use batch parameters.") require_exact_tool_versions = payload.require_exact_tool_versions - # require_exact_tool_versions = util.string_as_bool(payload.get("require_exact_tool_versions", "true")) tools = self._workflow_contents_manager.get_all_tools(workflow) missing_tools = [ tool @@ -162,7 +159,6 @@ def invoke_workflow( invocations = [] for run_config in run_configs: workflow_scheduler_id = payload.scheduler - # workflow_scheduler_id = payload.get("scheduler", None) # TODO: workflow scheduler hints work_request_params = dict(scheduler=workflow_scheduler_id) workflow_invocation = queue_invoke( From 2a39866d52b138d8d3c32cfaf601be271e2b7e9a Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Sun, 18 Feb 2024 18:30:49 +0100 Subject: [PATCH 27/57] Regenerate the client schema --- client/src/api/schema/schema.ts | 557 ++++++++++++++++++++++++++++++-- 1 file changed, 534 insertions(+), 23 deletions(-) diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 805bee90084b..6a15ed43d45c 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -1828,13 +1828,7 @@ export interface paths { "/api/workflows/{workflow_id}/invocations": { /** Get the list of a user's workflow invocations. */ get: operations["index_invocations_api_workflows__workflow_id__invocations_get"]; - /** - * Schedule the workflow specified by `workflow_id` to run. - * @description .. note:: This method takes the same arguments as - * :func:`galaxy.webapps.galaxy.api.workflows.WorkflowsAPIController.create` above. - * - * :raises: exceptions.MessageException, exceptions.RequestParameterInvalidException - */ + /** Schedule the workflow specified by `workflow_id` to run. */ post: operations["Invoke_workflow_api_workflows__workflow_id__invocations_post"]; }; "/api/workflows/{workflow_id}/invocations/{invocation_id}": { @@ -1896,6 +1890,10 @@ export interface paths { */ put: operations["publish_api_workflows__workflow_id__publish_put"]; }; + "/api/workflows/{workflow_id}/refactor": { + /** Updates the workflow stored with the given ID. */ + put: operations["refactor_api_workflows__workflow_id__refactor_put"]; + }; "/api/workflows/{workflow_id}/share_with_users": { /** * Share this item with specific users. @@ -1951,10 +1949,6 @@ export interface paths { /** * Schedule the workflow specified by `workflow_id` to run. * @deprecated - * @description .. note:: This method takes the same arguments as - * :func:`galaxy.webapps.galaxy.api.workflows.WorkflowsAPIController.create` above. - * - * :raises: exceptions.MessageException, exceptions.RequestParameterInvalidException */ post: operations["Invoke_workflow_api_workflows__workflow_id__usage_post"]; }; @@ -2114,6 +2108,63 @@ export interface components { */ link: string; }; + /** AddInputAction */ + AddInputAction: { + /** + * Action Type + * @constant + */ + action_type: "add_input"; + /** Collection Type */ + collection_type?: string | null; + /** Default */ + default?: Record | null; + /** Label */ + label?: string | null; + /** + * Optional + * @default false + */ + optional?: boolean | null; + position?: components["schemas"]["Position"] | null; + /** Restrict On Connections */ + restrict_on_connections?: boolean | null; + /** Restrictions */ + restrictions?: string[] | null; + /** Suggestions */ + suggestions?: string[] | null; + /** Type */ + type: string; + }; + /** + * AddStepAction + * @description Add a new action to the workflow. + * + * After the workflow is updated, an order_index will be assigned + * and this step may cause other steps to have their output_index + * adjusted. + */ + AddStepAction: { + /** + * Action Type + * @constant + */ + action_type: "add_step"; + /** + * Label + * @description A unique label for the step being added, must be distinct from the labels already present in the workflow. + */ + label?: string | null; + /** @description The location of the step in the Galaxy workflow editor. */ + position?: components["schemas"]["Position"] | null; + /** Tool State */ + tool_state?: Record | null; + /** + * Type + * @description Module type of the step to add, see galaxy.workflow.modules for available types. + */ + type: string; + }; /** AnonUserModel */ AnonUserModel: { /** @@ -2973,6 +3024,20 @@ export interface components { */ source: string | null; }; + /** ConnectAction */ + ConnectAction: { + /** + * Action Type + * @constant + */ + action_type: "connect"; + /** Input */ + input: components["schemas"]["InputReferenceByOrderIndex"] | components["schemas"]["InputReferenceByLabel"]; + /** Output */ + output: + | components["schemas"]["OutputReferenceByOrderIndex"] + | components["schemas"]["OutputReferenceByLabel"]; + }; /** ContentsObject */ ContentsObject: { /** @@ -4283,6 +4348,20 @@ export interface components { */ username: string; }; + /** DisconnectAction */ + DisconnectAction: { + /** + * Action Type + * @constant + */ + action_type: "disconnect"; + /** Input */ + input: components["schemas"]["InputReferenceByOrderIndex"] | components["schemas"]["InputReferenceByLabel"]; + /** Output */ + output: + | components["schemas"]["OutputReferenceByOrderIndex"] + | components["schemas"]["OutputReferenceByLabel"]; + }; /** * DisplayApp * @description Basic linked information about an application that can display certain datatypes. @@ -4739,6 +4818,32 @@ export interface components { * @enum {string} */ ExtraFilesEntryClass: "Directory" | "File"; + /** ExtractInputAction */ + ExtractInputAction: { + /** + * Action Type + * @constant + */ + action_type: "extract_input"; + /** Input */ + input: components["schemas"]["InputReferenceByOrderIndex"] | components["schemas"]["InputReferenceByLabel"]; + /** Label */ + label?: string | null; + position?: components["schemas"]["Position"] | null; + }; + /** ExtractUntypedParameter */ + ExtractUntypedParameter: { + /** + * Action Type + * @constant + */ + action_type: "extract_untyped_parameter"; + /** Label */ + label?: string | null; + /** Name */ + name: string; + position?: components["schemas"]["Position"] | null; + }; /** FavoriteObject */ FavoriteObject: { /** @@ -4832,6 +4937,14 @@ export interface components { */ to_posix_lines?: boolean; }; + /** FileDefaultsAction */ + FileDefaultsAction: { + /** + * Action Type + * @constant + */ + action_type: "fill_defaults"; + }; /** FileLibraryFolderItem */ FileLibraryFolderItem: { /** Can Manage */ @@ -4942,6 +5055,16 @@ export interface components { | components["schemas"]["BrowsableFilesSourcePlugin"] | components["schemas"]["FilesSourcePlugin"] )[]; + /** FillStepDefaultsAction */ + FillStepDefaultsAction: { + /** + * Action Type + * @constant + */ + action_type: "fill_step_defaults"; + /** Step */ + step: components["schemas"]["StepReferenceByOrderIndex"] | components["schemas"]["StepReferenceByLabel"]; + }; /** FolderLibraryFolderItem */ FolderLibraryFolderItem: { /** Can Manage */ @@ -6691,6 +6814,32 @@ export interface components { */ to_posix_lines?: "Yes" | boolean | null; }; + /** InputReferenceByLabel */ + InputReferenceByLabel: { + /** + * Input Name + * @description The input name as defined by the workflow module corresponding to the step being referenced. For Galaxy tool steps these inputs should be normalized using '|' (e.g. 'cond|repeat_0|input'). + */ + input_name: string; + /** + * Label + * @description The unique label of the step being referenced. + */ + label: string; + }; + /** InputReferenceByOrderIndex */ + InputReferenceByOrderIndex: { + /** + * Input Name + * @description The input name as defined by the workflow module corresponding to the step being referenced. For Galaxy tool steps these inputs should be normalized using '|' (e.g. 'cond|repeat_0|input'). + */ + input_name: string; + /** + * Order Index + * @description The order_index of the step being referenced. The order indices of a workflow start at 0. + */ + order_index: number; + }; /** InstalledRepositoryToolShedStatus */ InstalledRepositoryToolShedStatus: { /** @@ -7434,7 +7583,7 @@ export interface components { inputs_by?: string | null; /** * Is instance - * @description TODO + * @description True when fetching by Workflow ID, False when fetching by StoredWorkflow ID * @default false */ instance?: boolean | null; @@ -9083,6 +9232,34 @@ export interface components { */ url: string; }; + /** OutputReferenceByLabel */ + OutputReferenceByLabel: { + /** + * Label + * @description The unique label of the step being referenced. + */ + label: string; + /** + * Output Name + * @description The output name as defined by the workflow module corresponding to the step being referenced. The default is 'output', corresponding to the output defined by input step types. + * @default output + */ + output_name?: string | null; + }; + /** OutputReferenceByOrderIndex */ + OutputReferenceByOrderIndex: { + /** + * Order Index + * @description The order_index of the step being referenced. The order indices of a workflow start at 0. + */ + order_index: number; + /** + * Output Name + * @description The output name as defined by the workflow module corresponding to the step being referenced. The default is 'output', corresponding to the output defined by input step types. + * @default output + */ + output_name?: string | null; + }; /** * PageContentFormat * @enum {string} @@ -9396,6 +9573,13 @@ export interface components { * @enum {string} */ PluginKind: "rfs" | "drs" | "rdm" | "stock"; + /** Position */ + Position: { + /** Left */ + left: number; + /** Top */ + top: number; + }; /** PrepareStoreDownloadPayload */ PrepareStoreDownloadPayload: { /** @@ -9569,6 +9753,146 @@ export interface components { * @default [] */ QuotaSummaryList: components["schemas"]["QuotaSummary"][]; + /** RefactorActionExecution */ + RefactorActionExecution: { + /** Action */ + action: + | components["schemas"]["AddInputAction"] + | components["schemas"]["AddStepAction"] + | components["schemas"]["ConnectAction"] + | components["schemas"]["DisconnectAction"] + | components["schemas"]["ExtractInputAction"] + | components["schemas"]["ExtractUntypedParameter"] + | components["schemas"]["FileDefaultsAction"] + | components["schemas"]["FillStepDefaultsAction"] + | components["schemas"]["UpdateAnnotationAction"] + | components["schemas"]["UpdateCreatorAction"] + | components["schemas"]["UpdateNameAction"] + | components["schemas"]["UpdateLicenseAction"] + | components["schemas"]["UpdateOutputLabelAction"] + | components["schemas"]["UpdateReportAction"] + | components["schemas"]["UpdateStepLabelAction"] + | components["schemas"]["UpdateStepPositionAction"] + | components["schemas"]["UpgradeSubworkflowAction"] + | components["schemas"]["UpgradeToolAction"] + | components["schemas"]["UpgradeAllStepsAction"] + | components["schemas"]["RemoveUnlabeledWorkflowOutputs"]; + /** Messages */ + messages: components["schemas"]["RefactorActionExecutionMessage"][]; + }; + /** RefactorActionExecutionMessage */ + RefactorActionExecutionMessage: { + /** + * From Order Index + * @description For dropped connections these optional attributes refer to the output + * side of the connection that was dropped. + */ + from_order_index?: number | null; + /** + * From Step Label + * @description For dropped connections these optional attributes refer to the output + * side of the connection that was dropped. + */ + from_step_label?: string | null; + /** + * Input Name + * @description If this message is about an input to a step, + * this field describes the target input name. $The input name as defined by the workflow module corresponding to the step being referenced. For Galaxy tool steps these inputs should be normalized using '|' (e.g. 'cond|repeat_0|input'). + */ + input_name?: string | null; + /** Message */ + message: string; + message_type: components["schemas"]["RefactorActionExecutionMessageTypeEnum"]; + /** + * Order Index + * @description Reference to the step the message refers to. $ + * + * Messages don't have to be bound to a step, but if they are they will + * have a step_label and order_index included in the execution message. + * These are the label and order_index before applying the refactoring, + * the result of applying the action may change one or both of these. + * If connections are dropped this step reference will refer to the + * step with the previously connected input. + */ + order_index?: number | null; + /** + * Output Label + * @description If the message_type is workflow_output_drop_forced, this is the output label dropped. + */ + output_label?: string | null; + /** + * Output Name + * @description If this message is about an output to a step, + * this field describes the target output name. The output name as defined by the workflow module corresponding to the step being referenced. + */ + output_name?: string | null; + /** + * Step Label + * @description Reference to the step the message refers to. $ + * + * Messages don't have to be bound to a step, but if they are they will + * have a step_label and order_index included in the execution message. + * These are the label and order_index before applying the refactoring, + * the result of applying the action may change one or both of these. + * If connections are dropped this step reference will refer to the + * step with the previously connected input. + */ + step_label?: string | null; + }; + /** + * RefactorActionExecutionMessageTypeEnum + * @enum {string} + */ + RefactorActionExecutionMessageTypeEnum: + | "tool_version_change" + | "tool_state_adjustment" + | "connection_drop_forced" + | "workflow_output_drop_forced"; + /** RefactorRequest */ + RefactorRequest: { + /** Actions */ + actions: ( + | components["schemas"]["AddInputAction"] + | components["schemas"]["AddStepAction"] + | components["schemas"]["ConnectAction"] + | components["schemas"]["DisconnectAction"] + | components["schemas"]["ExtractInputAction"] + | components["schemas"]["ExtractUntypedParameter"] + | components["schemas"]["FileDefaultsAction"] + | components["schemas"]["FillStepDefaultsAction"] + | components["schemas"]["UpdateAnnotationAction"] + | components["schemas"]["UpdateCreatorAction"] + | components["schemas"]["UpdateNameAction"] + | components["schemas"]["UpdateLicenseAction"] + | components["schemas"]["UpdateOutputLabelAction"] + | components["schemas"]["UpdateReportAction"] + | components["schemas"]["UpdateStepLabelAction"] + | components["schemas"]["UpdateStepPositionAction"] + | components["schemas"]["UpgradeSubworkflowAction"] + | components["schemas"]["UpgradeToolAction"] + | components["schemas"]["UpgradeAllStepsAction"] + | components["schemas"]["RemoveUnlabeledWorkflowOutputs"] + )[]; + /** + * Dry Run + * @default false + */ + dry_run?: boolean; + /** + * Style + * @default export + */ + style?: string; + }; + /** RefactorResponse */ + RefactorResponse: { + /** Action Executions */ + action_executions: components["schemas"]["RefactorActionExecution"][]; + /** Dry Run */ + dry_run: boolean; + /** Workflow */ + workflow: string; + }; /** ReloadFeedback */ ReloadFeedback: { /** Failed */ @@ -9652,6 +9976,19 @@ export interface components { */ remote_user_email: string; }; + /** RemoveUnlabeledWorkflowOutputs */ + RemoveUnlabeledWorkflowOutputs: { + /** + * Action Type + * @constant + */ + action_type: "remove_unlabeled_workflow_outputs"; + }; + /** Report */ + Report: { + /** Markdown */ + markdown: string; + }; /** ReportJobErrorPayload */ ReportJobErrorPayload: { /** @@ -10321,6 +10658,22 @@ export interface components { * @enum {string} */ Src: "url" | "pasted" | "files" | "path" | "composite" | "ftp_import" | "server_dir"; + /** StepReferenceByLabel */ + StepReferenceByLabel: { + /** + * Label + * @description The unique label of the step being referenced. + */ + label: string; + }; + /** StepReferenceByOrderIndex */ + StepReferenceByOrderIndex: { + /** + * Order Index + * @description The order_index of the step being referenced. The order indices of a workflow start at 0. + */ + order_index: number; + }; /** StorageItemCleanupError */ StorageItemCleanupError: { /** Error */ @@ -10649,6 +11002,16 @@ export interface components { */ ids: string[]; }; + /** UpdateAnnotationAction */ + UpdateAnnotationAction: { + /** + * Action Type + * @constant + */ + action_type: "update_annotation"; + /** Annotation */ + annotation: string; + }; /** * UpdateCollectionAttributePayload * @description Contains attributes that can be updated for all elements in a dataset collection. @@ -10677,6 +11040,16 @@ export interface components { id: string; [key: string]: unknown | undefined; }; + /** UpdateCreatorAction */ + UpdateCreatorAction: { + /** + * Action Type + * @constant + */ + action_type: "update_creator"; + /** Creator */ + creator?: Record; + }; /** * UpdateHistoryContentsBatchPayload * @description Contains property values that will be updated for all the history `items` provided. @@ -10765,6 +11138,26 @@ export interface components { */ synopsis?: string | null; }; + /** UpdateLicenseAction */ + UpdateLicenseAction: { + /** + * Action Type + * @constant + */ + action_type: "update_license"; + /** License */ + license: string; + }; + /** UpdateNameAction */ + UpdateNameAction: { + /** + * Action Type + * @constant + */ + action_type: "update_name"; + /** Name */ + name: string; + }; /** UpdateObjectStoreIdPayload */ UpdateObjectStoreIdPayload: { /** @@ -10773,6 +11166,20 @@ export interface components { */ object_store_id: string; }; + /** UpdateOutputLabelAction */ + UpdateOutputLabelAction: { + /** + * Action Type + * @constant + */ + action_type: "update_output_label"; + /** Output */ + output: + | components["schemas"]["OutputReferenceByOrderIndex"] + | components["schemas"]["OutputReferenceByLabel"]; + /** Output Label */ + output_label: string; + }; /** UpdateQuotaParams */ UpdateQuotaParams: { /** @@ -10812,6 +11219,47 @@ export interface components { */ operation?: components["schemas"]["QuotaOperation"]; }; + /** UpdateReportAction */ + UpdateReportAction: { + /** + * Action Type + * @constant + */ + action_type: "update_report"; + report: components["schemas"]["Report"]; + }; + /** UpdateStepLabelAction */ + UpdateStepLabelAction: { + /** + * Action Type + * @constant + */ + action_type: "update_step_label"; + /** + * Label + * @description The unique label of the step being referenced. + */ + label: string; + /** + * Step + * @description The target step for this action. + */ + step: components["schemas"]["StepReferenceByOrderIndex"] | components["schemas"]["StepReferenceByLabel"]; + }; + /** UpdateStepPositionAction */ + UpdateStepPositionAction: { + /** + * Action Type + * @constant + */ + action_type: "update_step_position"; + position_shift: components["schemas"]["Position"]; + /** + * Step + * @description The target step for this action. + */ + step: components["schemas"]["StepReferenceByOrderIndex"] | components["schemas"]["StepReferenceByLabel"]; + }; /** * UpdateUserNotificationPreferencesRequest * @description Contains the new notification preferences of a user. @@ -10825,6 +11273,44 @@ export interface components { [key: string]: components["schemas"]["NotificationCategorySettings"] | undefined; }; }; + /** UpgradeAllStepsAction */ + UpgradeAllStepsAction: { + /** + * Action Type + * @constant + */ + action_type: "upgrade_all_steps"; + }; + /** UpgradeSubworkflowAction */ + UpgradeSubworkflowAction: { + /** + * Action Type + * @constant + */ + action_type: "upgrade_subworkflow"; + /** Content Id */ + content_id?: string | null; + /** + * Step + * @description The target step for this action. + */ + step: components["schemas"]["StepReferenceByOrderIndex"] | components["schemas"]["StepReferenceByLabel"]; + }; + /** UpgradeToolAction */ + UpgradeToolAction: { + /** + * Action Type + * @constant + */ + action_type: "upgrade_tool"; + /** + * Step + * @description The target step for this action. + */ + step: components["schemas"]["StepReferenceByOrderIndex"] | components["schemas"]["StepReferenceByLabel"]; + /** Tool Version */ + tool_version?: string | null; + }; /** UrlDataElement */ UrlDataElement: { /** Md5 */ @@ -21899,13 +22385,7 @@ export interface operations { }; }; Invoke_workflow_api_workflows__workflow_id__invocations_post: { - /** - * Schedule the workflow specified by `workflow_id` to run. - * @description .. note:: This method takes the same arguments as - * :func:`galaxy.webapps.galaxy.api.workflows.WorkflowsAPIController.create` above. - * - * :raises: exceptions.MessageException, exceptions.RequestParameterInvalidException - */ + /** Schedule the workflow specified by `workflow_id` to run. */ parameters: { /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ header?: { @@ -22255,6 +22735,41 @@ export interface operations { }; }; }; + refactor_api_workflows__workflow_id__refactor_put: { + /** Updates the workflow stored with the given ID. */ + parameters: { + query?: { + instance?: boolean | null; + }; + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string | null; + }; + /** @description The encoded database identifier of the Stored Workflow. */ + path: { + workflow_id: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RefactorRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["RefactorResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; share_with_users_api_workflows__workflow_id__share_with_users_put: { /** * Share this item with specific users. @@ -22610,10 +23125,6 @@ export interface operations { /** * Schedule the workflow specified by `workflow_id` to run. * @deprecated - * @description .. note:: This method takes the same arguments as - * :func:`galaxy.webapps.galaxy.api.workflows.WorkflowsAPIController.create` above. - * - * :raises: exceptions.MessageException, exceptions.RequestParameterInvalidException */ parameters: { /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ From 4e585aacec559374f3c55affceb02ee28381ba52 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Mon, 19 Feb 2024 21:31:57 +0100 Subject: [PATCH 28/57] Create service method for get_tool_predictions operation from WorkflowAPI --- .../webapps/galaxy/services/workflows.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/webapps/galaxy/services/workflows.py b/lib/galaxy/webapps/galaxy/services/workflows.py index 798bda20a576..5db50e7292a1 100644 --- a/lib/galaxy/webapps/galaxy/services/workflows.py +++ b/lib/galaxy/webapps/galaxy/services/workflows.py @@ -25,7 +25,8 @@ InvocationsStateCounts, WorkflowIndexQueryPayload, ) -from galaxy.schema.workflows import InvokeWorkflowPayload +from galaxy.schema.workflows import InvokeWorkflowPayload # GetToolPredictionsPayload, +from galaxy.tools import recommendations from galaxy.util.tool_shed.tool_shed_registry import Registry from galaxy.webapps.galaxy.services.base import ServiceBase from galaxy.webapps.galaxy.services.sharable import ShareableService @@ -53,6 +54,21 @@ def __init__( self._serializer = serializer self.shareable_service = ShareableService(workflows_manager, serializer, notification_manager) self._tool_shed_registry = tool_shed_registry + self.tool_recommendations = recommendations.ToolRecommendations() + + def get_tool_predictions( + self, + trans: ProvidesUserContext, + payload, + ): + remote_model_url = payload.get("remote_model_url", trans.app.config.tool_recommendation_model_path) + tool_sequence = payload.get("tool_sequence", "") + if "tool_sequence" not in payload or remote_model_url is None: + return {} + tool_sequence, recommended_tools = self.tool_recommendations.get_predictions( + trans, tool_sequence, remote_model_url + ) + return {"current_tool": tool_sequence, "predicted_data": recommended_tools} def index( self, From bd7dca7ab1e7eed32ac23d71754decb3537aec0c Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Mon, 19 Feb 2024 21:32:36 +0100 Subject: [PATCH 29/57] Add payload model for get_tool_predictions operation from WorkflowAPI --- lib/galaxy/schema/workflows.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/galaxy/schema/workflows.py b/lib/galaxy/schema/workflows.py index 7980fdd19d6a..87f97012066d 100644 --- a/lib/galaxy/schema/workflows.py +++ b/lib/galaxy/schema/workflows.py @@ -70,6 +70,19 @@ class GetTargetHistoryPayload(Model): ) +class GetToolPredictionsPayload(Model): + tool_sequence: Any = Field( + ..., + title="Tool Sequence", + description="comma separated sequence of tool ids", + ) + remote_model_url: Optional[Any] = Field( + None, + title="Remote Model URL", + description="Path to the deep learning model", + ) + + class InvokeWorkflowPayload(GetTargetHistoryPayload): # TODO - Are the descriptions correct? instance: Optional[bool] = Field( From cab62272446738ed63195770dd1a1284295032df Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Mon, 19 Feb 2024 21:33:55 +0100 Subject: [PATCH 30/57] Refactor get_tool_predictions operation --- lib/galaxy/webapps/galaxy/api/workflows.py | 48 +++++++++++----------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index f5d506599cf8..6c3aad52eb7b 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -80,7 +80,10 @@ SharingStatus, WorkflowSortByEnum, ) -from galaxy.schema.workflows import InvokeWorkflowPayload +from galaxy.schema.workflows import ( + GetToolPredictionsPayload, + InvokeWorkflowPayload, +) from galaxy.structured_app import StructuredApp from galaxy.tool_shed.galaxy_install.install_manager import InstallRepositoryManager from galaxy.tools import recommendations @@ -587,29 +590,6 @@ def build_module(self, trans: GalaxyWebTransaction, payload=None): step_dict["tool_version"] = module.get_version() return step_dict - @expose_api - def get_tool_predictions(self, trans: ProvidesUserContext, payload, **kwd): - """ - POST /api/workflows/get_tool_predictions - - Fetch predicted tools for a workflow - - :type payload: dict - :param payload: - - a dictionary containing two parameters - 'tool_sequence' - comma separated sequence of tool ids - 'remote_model_url' - (optional) path to the deep learning model - """ - remote_model_url = payload.get("remote_model_url", trans.app.config.tool_recommendation_model_path) - tool_sequence = payload.get("tool_sequence", "") - if "tool_sequence" not in payload or remote_model_url is None: - return - tool_sequence, recommended_tools = self.tool_recommendations.get_predictions( - trans, tool_sequence, remote_model_url - ) - return {"current_tool": tool_sequence, "predicted_data": recommended_tools} - # # -- Helper methods -- # @@ -909,6 +889,15 @@ def __get_stored_workflow(self, trans, workflow_id, **kwd): ), ] +GetToolPredictionsBody = Annotated[ + GetToolPredictionsPayload, + Body( + default=..., + title="Get tool predictions", + description="The values to get tool predictions for a workflow.", + ), +] + @router.cbv class FastAPIWorkflows: @@ -955,6 +944,17 @@ def index( response.headers["total_matches"] = str(total_matches) return workflows + @router.post( + "/api/workflows/get_tool_predictions", + summary="Fetch predicted tools for a workflow", + ) + def get_tool_predictions( + self, + payload: GetToolPredictionsBody, + trans: ProvidesUserContext = DependsOnTrans, + ): + return self.service.get_tool_predictions(trans, payload.model_dump(exclude_unset=True)) + @router.get( "/api/workflows/{workflow_id}/sharing", summary="Get the current sharing status of the given item.", From 5ae614ad38e1ba25be545e93bb82bfa605ddc514 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Mon, 19 Feb 2024 21:34:24 +0100 Subject: [PATCH 31/57] Remove mapping to the legacy route --- lib/galaxy/webapps/galaxy/buildapp.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py index 0430f96d0d3c..0eceb3b56609 100644 --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -580,12 +580,6 @@ def populate_api_routes(webapp, app): controller="container_resolution", conditions=dict(method=["POST"]), ) - webapp.mapper.connect( - "/api/workflows/get_tool_predictions", - action="get_tool_predictions", - controller="workflows", - conditions=dict(method=["POST"]), - ) webapp.mapper.resource("visualization", "visualizations", path_prefix="/api") webapp.mapper.resource("plugins", "plugins", path_prefix="/api") From 1897dc2a3bf94e57d0191f47023cb45689412e0e Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Mon, 19 Feb 2024 21:34:39 +0100 Subject: [PATCH 32/57] Regenerate the client schema --- client/src/api/schema/schema.ts | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 6a15ed43d45c..e37c6886a85e 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -1799,6 +1799,10 @@ export interface paths { */ get: operations["index_api_workflows_get"]; }; + "/api/workflows/get_tool_predictions": { + /** Fetch predicted tools for a workflow */ + post: operations["get_tool_predictions_api_workflows_get_tool_predictions_post"]; + }; "/api/workflows/menu": { /** Get workflows present in the tools panel. */ get: operations["get_workflow_menu_api_workflows_menu_get"]; @@ -5185,6 +5189,19 @@ export interface components { /** Tags */ tags?: string[] | null; }; + /** GetToolPredictionsPayload */ + GetToolPredictionsPayload: { + /** + * Remote Model URL + * @description Path to the deep learning model + */ + remote_model_url?: Record | null; + /** + * Tool Sequence + * @description comma separated sequence of tool ids + */ + tool_sequence: Record; + }; /** * GroupCreatePayload * @description Payload schema for creating a group. @@ -22182,6 +22199,34 @@ export interface operations { }; }; }; + get_tool_predictions_api_workflows_get_tool_predictions_post: { + /** Fetch predicted tools for a workflow */ + parameters?: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GetToolPredictionsPayload"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": Record; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; get_workflow_menu_api_workflows_menu_get: { /** Get workflows present in the tools panel. */ parameters?: { From 4e1fda9c7bffd3f8d806a97a9e0686c87ed00241 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Tue, 20 Feb 2024 21:16:29 +0100 Subject: [PATCH 33/57] Create service method for show_workflow operation from the Workflows API --- .../webapps/galaxy/services/workflows.py | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/webapps/galaxy/services/workflows.py b/lib/galaxy/webapps/galaxy/services/workflows.py index 5db50e7292a1..15ea34ecf1ea 100644 --- a/lib/galaxy/webapps/galaxy/services/workflows.py +++ b/lib/galaxy/webapps/galaxy/services/workflows.py @@ -10,7 +10,10 @@ from fastapi import HTTPException -from galaxy import web +from galaxy import ( + exceptions, + web, +) from galaxy.managers.context import ProvidesUserContext from galaxy.managers.notification import NotificationManager from galaxy.managers.workflows import ( @@ -227,6 +230,26 @@ def get_workflow_menu(self, trans, payload): ) return {"ids_in_menu": ids_in_menu, "workflows": workflows} + def show_workflow(self, trans, workflow_id, instance, legacy, version): + stored_workflow = self._workflows_manager.get_stored_workflow(trans, workflow_id, by_stored_id=not instance) + if stored_workflow.importable is False and stored_workflow.user != trans.user and not trans.user_is_admin: + wf_count = 0 if not trans.user else trans.user.count_stored_workflow_user_assocs(stored_workflow) + if wf_count == 0: + message = "Workflow is neither importable, nor owned by or shared with current user" + raise exceptions.ItemAccessibilityException(message) + if legacy: + style = "legacy" + else: + style = "instance" + if version is None and instance: + # A Workflow instance may not be the latest workflow version attached to StoredWorkflow. + # This figures out the correct version so that we return the correct Workflow and version. + for i, workflow in enumerate(reversed(stored_workflow.workflows)): + if workflow.id == workflow_id: + version = i + break + return self._workflow_contents_manager.workflow_to_dict(trans, stored_workflow, style=style, version=version) + def _get_workflows_list( self, trans: ProvidesUserContext, From 92855f68aa76588c1fbcacc639e89a4a0059a8b5 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Tue, 20 Feb 2024 21:16:58 +0100 Subject: [PATCH 34/57] Refactor show operation --- lib/galaxy/webapps/galaxy/api/workflows.py | 64 +++++++++++----------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 6c3aad52eb7b..424bec1053af 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -93,7 +93,6 @@ from galaxy.version import VERSION from galaxy.web import ( expose_api, - expose_api_anonymous_and_sessionless, expose_api_raw_anonymous_and_sessionless, format_return_as_json, ) @@ -191,38 +190,6 @@ def set_workflow_menu(self, trans: GalaxyWebTransaction, payload=None, **kwd): trans.set_message(message) return {"message": message, "status": "done"} - @expose_api_anonymous_and_sessionless - def show(self, trans: GalaxyWebTransaction, id, **kwd): - """ - GET /api/workflows/{encoded_workflow_id} - - :param instance: true if fetch by Workflow ID instead of StoredWorkflow id, false - by default. - :type instance: boolean - - Displays information needed to run a workflow. - """ - stored_workflow = self.__get_stored_workflow(trans, id, **kwd) - if stored_workflow.importable is False and stored_workflow.user != trans.user and not trans.user_is_admin: - wf_count = 0 if not trans.user else trans.user.count_stored_workflow_user_assocs(stored_workflow) - if wf_count == 0: - message = "Workflow is neither importable, nor owned by or shared with current user" - raise exceptions.ItemAccessibilityException(message) - if kwd.get("legacy", False): - style = "legacy" - else: - style = "instance" - version = kwd.get("version") - if version is None and util.string_as_bool(kwd.get("instance", "false")): - # A Workflow instance may not be the latest workflow version attached to StoredWorkflow. - # This figures out the correct version so that we return the correct Workflow and version. - workflow_id = self.decode_id(id) - for i, workflow in enumerate(reversed(stored_workflow.workflows)): - if workflow.id == workflow_id: - version = i - break - return self.workflow_contents_manager.workflow_to_dict(trans, stored_workflow, style=style, version=version) - @expose_api def create(self, trans: GalaxyWebTransaction, payload=None, **kwd): """ @@ -841,6 +808,22 @@ def __get_stored_workflow(self, trans, workflow_id, **kwd): ), ] +LegacyQueryParam = Annotated[ + Optional[bool], + Query( + title="Legacy", + description="Use the legacy workflow format.", + ), +] + +VersionQueryParam = Annotated[ + Optional[int], + Query( + title="Version", + description="The version of the workflow to fetch.", + ), +] + query_tags = [ IndexQueryTag("name", "The stored workflow's name.", "n"), IndexQueryTag( @@ -1151,6 +1134,21 @@ def get_workflow_menu( payload=payload, ) + @router.get( + "/api/workflows/{encoded_workflow_id}", + summary="Displays information needed to run a workflow.", + name="show_workflow", + ) + def show_workflow( + self, + encoded_workflow_id: StoredWorkflowIDPathParam, + instance: InstanceQueryParam = False, + legacy: LegacyQueryParam = False, + version: VersionQueryParam = None, + trans: ProvidesHistoryContext = DependsOnTrans, + ): + return self.service.show_workflow(trans, encoded_workflow_id, instance, legacy, version) + StepDetailQueryParam = Annotated[ bool, From a9276a1acadce6ddfb4b0f695b0f19627dabd323 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Tue, 20 Feb 2024 21:17:16 +0100 Subject: [PATCH 35/57] Remove mapping to the legacy route --- lib/galaxy/webapps/galaxy/buildapp.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py index 0eceb3b56609..704b8f8c3e28 100644 --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -680,12 +680,6 @@ def populate_api_routes(webapp, app): # action="import_tool_version", # conditions=dict(method=["POST"]), # ) - webapp.mapper.connect( - "/api/workflows/{encoded_workflow_id}", - controller="workflows", - action="show", - conditions=dict(method=["GET"]), - ) webapp.mapper.connect( "/api/workflows/{encoded_workflow_id}", controller="workflows", From 141e5f5fea0476b168b2a850788fb3b66ec8caaf Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Tue, 20 Feb 2024 21:18:29 +0100 Subject: [PATCH 36/57] Regenerate the client schema --- client/src/api/schema/schema.ts | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index e37c6886a85e..de2ff0b6cdb4 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -1807,6 +1807,10 @@ export interface paths { /** Get workflows present in the tools panel. */ get: operations["get_workflow_menu_api_workflows_menu_get"]; }; + "/api/workflows/{encoded_workflow_id}": { + /** Displays information needed to run a workflow. */ + get: operations["show_workflow_api_workflows__encoded_workflow_id__get"]; + }; "/api/workflows/{workflow_id}": { /** Add the deleted flag to a workflow. */ delete: operations["delete_workflow_api_workflows__workflow_id__delete"]; @@ -22260,6 +22264,40 @@ export interface operations { }; }; }; + show_workflow_api_workflows__encoded_workflow_id__get: { + /** Displays information needed to run a workflow. */ + parameters: { + /** @description Use the legacy workflow format. */ + /** @description The version of the workflow to fetch. */ + query?: { + instance?: boolean | null; + legacy?: boolean | null; + version?: number | null; + }; + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string | null; + }; + /** @description The encoded database identifier of the Stored Workflow. */ + path: { + encoded_workflow_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": Record; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; delete_workflow_api_workflows__workflow_id__delete: { /** Add the deleted flag to a workflow. */ parameters: { From f3ffc33c0d8a30dd43efb88fd36b17839a0fd710 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Wed, 21 Feb 2024 18:53:25 +0100 Subject: [PATCH 37/57] Rename path parameter of show_worfklow operation --- lib/galaxy/webapps/galaxy/api/workflows.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 424bec1053af..22f14e7a6d38 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -1135,19 +1135,19 @@ def get_workflow_menu( ) @router.get( - "/api/workflows/{encoded_workflow_id}", + "/api/workflows/{workflow_id}", summary="Displays information needed to run a workflow.", name="show_workflow", ) def show_workflow( self, - encoded_workflow_id: StoredWorkflowIDPathParam, + workflow_id: StoredWorkflowIDPathParam, instance: InstanceQueryParam = False, legacy: LegacyQueryParam = False, version: VersionQueryParam = None, trans: ProvidesHistoryContext = DependsOnTrans, ): - return self.service.show_workflow(trans, encoded_workflow_id, instance, legacy, version) + return self.service.show_workflow(trans, workflow_id, instance, legacy, version) StepDetailQueryParam = Annotated[ From b2fd59daba4d2261380cbbbb14ea6e888001c7ef Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Wed, 21 Feb 2024 18:59:30 +0100 Subject: [PATCH 38/57] Move pydantic model StoredWorkflowDetailed to schema file of workflows --- lib/galaxy/schema/schema.py | 29 ----------------------- lib/galaxy/schema/workflows.py | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/lib/galaxy/schema/schema.py b/lib/galaxy/schema/schema.py index f7342f60be9c..50201b55c36a 100644 --- a/lib/galaxy/schema/schema.py +++ b/lib/galaxy/schema/schema.py @@ -2344,35 +2344,6 @@ class Person(Creator): ) -class StoredWorkflowDetailed(StoredWorkflowSummary): - annotation: Optional[str] = AnnotationField # Inconsistency? See comment on StoredWorkflowSummary.annotations - license: Optional[str] = Field( - None, title="License", description="SPDX Identifier of the license associated with this workflow." - ) - version: int = Field( - ..., title="Version", description="The version of the workflow represented by an incremental number." - ) - inputs: Dict[int, WorkflowInput] = Field( - {}, title="Inputs", description="A dictionary containing information about all the inputs of the workflow." - ) - creator: Optional[List[Union[Person, Organization]]] = Field( - None, - title="Creator", - description=("Additional information about the creator (or multiple creators) of this workflow."), - ) - steps: Dict[ - int, - Union[ - InputDataStep, - InputDataCollectionStep, - InputParameterStep, - PauseStep, - ToolStep, - SubworkflowStep, - ], - ] = Field({}, title="Steps", description="A dictionary with information about all the steps of the workflow.") - - class Input(Model): name: str = Field(..., title="Name", description="The name of the input.") description: str = Field(..., title="Description", description="The annotation or description of the input.") diff --git a/lib/galaxy/schema/workflows.py b/lib/galaxy/schema/workflows.py index 87f97012066d..213aff503b3d 100644 --- a/lib/galaxy/schema/workflows.py +++ b/lib/galaxy/schema/workflows.py @@ -2,7 +2,9 @@ from typing import ( Any, Dict, + List, Optional, + Union, ) from pydantic import ( @@ -11,8 +13,19 @@ ) from galaxy.schema.schema import ( + AnnotationField, + InputDataCollectionStep, + InputDataStep, + InputParameterStep, Model, + Organization, + PauseStep, + Person, PreferredObjectStoreIdField, + StoredWorkflowSummary, + SubworkflowStep, + ToolStep, + WorkflowInput, ) # class WorkflowExtractionParams(Model): @@ -201,3 +214,32 @@ def inputs_string_to_json(cls, v): description="The ID of the object store that should be used to store ? datasets in this history.", ) preferred_object_store_id: Optional[str] = PreferredObjectStoreIdField + + +class StoredWorkflowDetailed(StoredWorkflowSummary): + annotation: Optional[str] = AnnotationField # Inconsistency? See comment on StoredWorkflowSummary.annotations + license: Optional[str] = Field( + None, title="License", description="SPDX Identifier of the license associated with this workflow." + ) + version: int = Field( + ..., title="Version", description="The version of the workflow represented by an incremental number." + ) + inputs: Dict[int, WorkflowInput] = Field( + {}, title="Inputs", description="A dictionary containing information about all the inputs of the workflow." + ) + creator: Optional[List[Union[Person, Organization]]] = Field( + None, + title="Creator", + description=("Additional information about the creator (or multiple creators) of this workflow."), + ) + steps: Dict[ + int, + Union[ + InputDataStep, + InputDataCollectionStep, + InputParameterStep, + PauseStep, + ToolStep, + SubworkflowStep, + ], + ] = Field({}, title="Steps", description="A dictionary with information about all the steps of the workflow.") From 66e0b6c98c115d7cac7abc2654776ef1f0f6149c Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Wed, 21 Feb 2024 19:00:55 +0100 Subject: [PATCH 39/57] Add missing fields to pydantic model inorder to use it in show_workflows operation --- lib/galaxy/schema/workflows.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/galaxy/schema/workflows.py b/lib/galaxy/schema/workflows.py index 213aff503b3d..0c005387e4e8 100644 --- a/lib/galaxy/schema/workflows.py +++ b/lib/galaxy/schema/workflows.py @@ -243,3 +243,24 @@ class StoredWorkflowDetailed(StoredWorkflowSummary): SubworkflowStep, ], ] = Field({}, title="Steps", description="A dictionary with information about all the steps of the workflow.") + # TODO - Check descriptions of newly added fields + importable: Optional[bool] = Field( + ..., + title="Importable", + description="Indicates if the workflow is importable by ?.", + ) + email_hash: Optional[str] = Field( + ..., + title="Email Hash", + description="The hash of the email of ?", + ) + slug: Optional[str] = Field( + ..., + title="Slug", + description="The slug of the workflow.", + ) + source_metadata: Optional[str] = Field( + ..., + title="Source Metadata", + description="The source metadata of the workflow.", + ) From a86661daa9de99e08cc7289ad63dfbe1591345ca Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Wed, 21 Feb 2024 19:02:20 +0100 Subject: [PATCH 40/57] Make fields of Workflows model optional inorder to use it in show_workflows operation --- lib/galaxy/schema/schema.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/galaxy/schema/schema.py b/lib/galaxy/schema/schema.py index 50201b55c36a..705438ee3f1c 100644 --- a/lib/galaxy/schema/schema.py +++ b/lib/galaxy/schema/schema.py @@ -2112,10 +2112,12 @@ class StoredWorkflowSummary(Model, WithModelClass): title="Published", description="Whether this workflow is currently publicly available to all users.", ) - annotations: List[str] = Field( # Inconsistency? Why workflows summaries use a list instead of an optional string? - ..., - title="Annotations", - description="An list of annotations to provide details or to help understand the purpose and usage of this workflow.", + annotations: Optional[List[str]] = ( + Field( # Inconsistency? Why workflows summaries use a list instead of an optional string? + None, + title="Annotations", + description="An list of annotations to provide details or to help understand the purpose and usage of this workflow.", + ) ) tags: TagCollection deleted: bool = Field( @@ -2138,13 +2140,13 @@ class StoredWorkflowSummary(Model, WithModelClass): title="Latest workflow UUID", description="TODO", ) - number_of_steps: int = Field( - ..., + number_of_steps: Optional[int] = Field( + None, title="Number of Steps", description="The number of steps that make up this workflow.", ) - show_in_tool_panel: bool = Field( - ..., + show_in_tool_panel: Optional[bool] = Field( + None, title="Show in Tool Panel", description="Whether to display this workflow in the Tools Panel.", ) From f91dd9755e5ce0ce794359eb30774af6ba580e30 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Wed, 21 Feb 2024 19:03:40 +0100 Subject: [PATCH 41/57] Add pydantic model to return of show_workflows operation --- lib/galaxy/webapps/galaxy/api/workflows.py | 3 ++- lib/galaxy/webapps/galaxy/services/workflows.py | 12 +++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 22f14e7a6d38..8e6d90dbf555 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -83,6 +83,7 @@ from galaxy.schema.workflows import ( GetToolPredictionsPayload, InvokeWorkflowPayload, + StoredWorkflowDetailed, ) from galaxy.structured_app import StructuredApp from galaxy.tool_shed.galaxy_install.install_manager import InstallRepositoryManager @@ -1146,7 +1147,7 @@ def show_workflow( legacy: LegacyQueryParam = False, version: VersionQueryParam = None, trans: ProvidesHistoryContext = DependsOnTrans, - ): + ) -> StoredWorkflowDetailed: return self.service.show_workflow(trans, workflow_id, instance, legacy, version) diff --git a/lib/galaxy/webapps/galaxy/services/workflows.py b/lib/galaxy/webapps/galaxy/services/workflows.py index 15ea34ecf1ea..b75f8916817a 100644 --- a/lib/galaxy/webapps/galaxy/services/workflows.py +++ b/lib/galaxy/webapps/galaxy/services/workflows.py @@ -28,7 +28,10 @@ InvocationsStateCounts, WorkflowIndexQueryPayload, ) -from galaxy.schema.workflows import InvokeWorkflowPayload # GetToolPredictionsPayload, +from galaxy.schema.workflows import ( # GetToolPredictionsPayload, + InvokeWorkflowPayload, + StoredWorkflowDetailed, +) from galaxy.tools import recommendations from galaxy.util.tool_shed.tool_shed_registry import Registry from galaxy.webapps.galaxy.services.base import ServiceBase @@ -230,7 +233,7 @@ def get_workflow_menu(self, trans, payload): ) return {"ids_in_menu": ids_in_menu, "workflows": workflows} - def show_workflow(self, trans, workflow_id, instance, legacy, version): + def show_workflow(self, trans, workflow_id, instance, legacy, version) -> StoredWorkflowDetailed: stored_workflow = self._workflows_manager.get_stored_workflow(trans, workflow_id, by_stored_id=not instance) if stored_workflow.importable is False and stored_workflow.user != trans.user and not trans.user_is_admin: wf_count = 0 if not trans.user else trans.user.count_stored_workflow_user_assocs(stored_workflow) @@ -248,7 +251,10 @@ def show_workflow(self, trans, workflow_id, instance, legacy, version): if workflow.id == workflow_id: version = i break - return self._workflow_contents_manager.workflow_to_dict(trans, stored_workflow, style=style, version=version) + detailed_workflow = StoredWorkflowDetailed( + **self._workflow_contents_manager.workflow_to_dict(trans, stored_workflow, style=style, version=version) + ) + return detailed_workflow def _get_workflows_list( self, From c18366ab866762eece7f6944f8d05c291c741e35 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Wed, 21 Feb 2024 19:09:33 +0100 Subject: [PATCH 42/57] Regenerate the client schema --- client/src/api/schema/schema.ts | 534 ++++++++++++++++++++++++++++++-- 1 file changed, 512 insertions(+), 22 deletions(-) diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index de2ff0b6cdb4..a0572fe56d59 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -1807,11 +1807,9 @@ export interface paths { /** Get workflows present in the tools panel. */ get: operations["get_workflow_menu_api_workflows_menu_get"]; }; - "/api/workflows/{encoded_workflow_id}": { - /** Displays information needed to run a workflow. */ - get: operations["show_workflow_api_workflows__encoded_workflow_id__get"]; - }; "/api/workflows/{workflow_id}": { + /** Displays information needed to run a workflow. */ + get: operations["show_workflow_api_workflows__workflow_id__get"]; /** Add the deleted flag to a workflow. */ delete: operations["delete_workflow_api_workflows__workflow_id__delete"]; }; @@ -6835,6 +6833,129 @@ export interface components { */ to_posix_lines?: "Yes" | boolean | null; }; + /** InputDataCollectionStep */ + InputDataCollectionStep: { + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation: string | null; + /** + * ID + * @description The identifier of the step. It matches the index order of the step inside the workflow. + */ + id: number; + /** + * Input Steps + * @description A dictionary containing information about the inputs connected to this workflow step. + */ + input_steps: { + [key: string]: components["schemas"]["InputStep"] | undefined; + }; + /** + * Tool ID + * @description The unique name of the tool associated with this step. + */ + tool_id?: string | null; + /** + * Tool Inputs + * @description TODO + */ + tool_inputs?: Record; + /** + * Tool Version + * @description The version of the tool associated with this step. + */ + tool_version?: string | null; + /** + * Type + * @description The type of workflow module. + * @default data_collection_input + */ + type?: components["schemas"]["WorkflowModuleType"]; + }; + /** InputDataStep */ + InputDataStep: { + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation: string | null; + /** + * ID + * @description The identifier of the step. It matches the index order of the step inside the workflow. + */ + id: number; + /** + * Input Steps + * @description A dictionary containing information about the inputs connected to this workflow step. + */ + input_steps: { + [key: string]: components["schemas"]["InputStep"] | undefined; + }; + /** + * Tool ID + * @description The unique name of the tool associated with this step. + */ + tool_id?: string | null; + /** + * Tool Inputs + * @description TODO + */ + tool_inputs?: Record; + /** + * Tool Version + * @description The version of the tool associated with this step. + */ + tool_version?: string | null; + /** + * Type + * @description The type of workflow module. + * @default data_input + */ + type?: components["schemas"]["WorkflowModuleType"]; + }; + /** InputParameterStep */ + InputParameterStep: { + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation: string | null; + /** + * ID + * @description The identifier of the step. It matches the index order of the step inside the workflow. + */ + id: number; + /** + * Input Steps + * @description A dictionary containing information about the inputs connected to this workflow step. + */ + input_steps: { + [key: string]: components["schemas"]["InputStep"] | undefined; + }; + /** + * Tool ID + * @description The unique name of the tool associated with this step. + */ + tool_id?: string | null; + /** + * Tool Inputs + * @description TODO + */ + tool_inputs?: Record; + /** + * Tool Version + * @description The version of the tool associated with this step. + */ + tool_version?: string | null; + /** + * Type + * @description The type of workflow module. + * @default parameter_input + */ + type?: components["schemas"]["WorkflowModuleType"]; + }; /** InputReferenceByLabel */ InputReferenceByLabel: { /** @@ -6861,6 +6982,19 @@ export interface components { */ order_index: number; }; + /** InputStep */ + InputStep: { + /** + * Source Step + * @description The identifier of the workflow step connected to this particular input. + */ + source_step: number; + /** + * Step Output + * @description The name of the output generated by the source step. + */ + step_output: string; + }; /** InstalledRepositoryToolShedStatus */ InstalledRepositoryToolShedStatus: { /** @@ -9239,20 +9373,6 @@ export interface components { */ up_to_date: boolean; }; - /** Organization */ - Organization: { - /** - * Name - * @description Name of the organization responsible for the service - */ - name: string; - /** - * Url - * Format: uri - * @description URL of the website of the organization (RFC 3986 format) - */ - url: string; - }; /** OutputReferenceByLabel */ OutputReferenceByLabel: { /** @@ -9581,6 +9701,80 @@ export interface components { */ to_posix_lines?: boolean; }; + /** PauseStep */ + PauseStep: { + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation: string | null; + /** + * ID + * @description The identifier of the step. It matches the index order of the step inside the workflow. + */ + id: number; + /** + * Input Steps + * @description A dictionary containing information about the inputs connected to this workflow step. + */ + input_steps: { + [key: string]: components["schemas"]["InputStep"] | undefined; + }; + /** + * Type + * @description The type of workflow module. + * @default pause + */ + type?: components["schemas"]["WorkflowModuleType"]; + }; + /** Person */ + Person: { + /** Address */ + address?: string | null; + /** Alternate Name */ + alternateName?: string | null; + /** + * Class + * @default Person + */ + class?: string; + /** Email */ + email?: string | null; + /** Family Name */ + familyName?: string | null; + /** Fax Number */ + faxNumber?: string | null; + /** Given Name */ + givenName?: string | null; + /** + * Honorific Prefix + * @description Honorific Prefix (e.g. Dr/Mrs/Mr) + */ + honorificPrefix?: string | null; + /** + * Honorific Suffix + * @description Honorific Suffix (e.g. M.D.) + */ + honorificSuffix?: string | null; + /** + * Identifier + * @description Identifier (typically an orcid.org ID) + */ + identifier?: string | null; + /** Image URL */ + image?: string | null; + /** Job Title */ + jobTitle?: string | null; + /** + * Name + * @description The name of the creator. + */ + name: string; + /** Telephone */ + telephone?: string | null; + /** URL */ + url?: string | null; + }; /** * PersonalNotificationCategory * @description These notification categories can be opt-out by the user and will be @@ -10227,7 +10421,7 @@ export interface components { */ name: string; /** @description Organization providing the service */ - organization: components["schemas"]["Organization"]; + organization: components["schemas"]["galaxy__schema__drs__Organization"]; type: components["schemas"]["ServiceType"]; /** * Updatedat @@ -10768,6 +10962,184 @@ export interface components { * @enum {string} */ StoredItemOrderBy: "name-asc" | "name-dsc" | "size-asc" | "size-dsc" | "update_time-asc" | "update_time-dsc"; + /** StoredWorkflowDetailed */ + StoredWorkflowDetailed: { + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation: string | null; + /** + * Annotations + * @description An list of annotations to provide details or to help understand the purpose and usage of this workflow. + */ + annotations?: string[] | null; + /** + * Create Time + * Format: date-time + * @description The time and date this item was created. + */ + create_time: string; + /** + * Creator + * @description Additional information about the creator (or multiple creators) of this workflow. + */ + creator?: + | (components["schemas"]["Person"] | components["schemas"]["galaxy__schema__schema__Organization"])[] + | null; + /** + * Deleted + * @description Whether this item is marked as deleted. + */ + deleted: boolean; + /** + * Email Hash + * @description The hash of the email of ? + */ + email_hash: string | null; + /** + * Hidden + * @description TODO + */ + hidden: boolean; + /** + * Id + * @example 0123456789ABCDEF + */ + id: string; + /** + * Importable + * @description Indicates if the workflow is importable by ?. + */ + importable: boolean | null; + /** + * Inputs + * @description A dictionary containing information about all the inputs of the workflow. + * @default {} + */ + inputs?: { + [key: string]: components["schemas"]["WorkflowInput"] | undefined; + }; + /** + * Latest workflow UUID + * Format: uuid4 + * @description TODO + */ + latest_workflow_uuid: string; + /** + * License + * @description SPDX Identifier of the license associated with this workflow. + */ + license?: string | null; + /** + * Model class + * @description The name of the database model class. + * @constant + */ + model_class: "StoredWorkflow"; + /** + * Name + * @description The name of the history. + */ + name: string; + /** + * Number of Steps + * @description The number of steps that make up this workflow. + */ + number_of_steps?: number | null; + /** + * Owner + * @description The name of the user who owns this workflow. + */ + owner: string; + /** + * Published + * @description Whether this workflow is currently publicly available to all users. + */ + published: boolean; + /** + * Show in Tool Panel + * @description Whether to display this workflow in the Tools Panel. + */ + show_in_tool_panel?: boolean | null; + /** + * Slug + * @description The slug of the workflow. + */ + slug: string | null; + /** + * Source Metadata + * @description The source metadata of the workflow. + */ + source_metadata: string | null; + /** + * Steps + * @description A dictionary with information about all the steps of the workflow. + * @default {} + */ + steps?: { + [key: string]: + | ( + | components["schemas"]["InputDataStep"] + | components["schemas"]["InputDataCollectionStep"] + | components["schemas"]["InputParameterStep"] + | components["schemas"]["PauseStep"] + | components["schemas"]["ToolStep"] + | components["schemas"]["SubworkflowStep"] + ) + | undefined; + }; + tags: components["schemas"]["TagCollection"]; + /** + * Update Time + * Format: date-time + * @description The last time and date this item was updated. + */ + update_time: string; + /** + * URL + * @deprecated + * @description The relative URL to access this item. + */ + url: string; + /** + * Version + * @description The version of the workflow represented by an incremental number. + */ + version: number; + }; + /** SubworkflowStep */ + SubworkflowStep: { + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation: string | null; + /** + * ID + * @description The identifier of the step. It matches the index order of the step inside the workflow. + */ + id: number; + /** + * Input Steps + * @description A dictionary containing information about the inputs connected to this workflow step. + */ + input_steps: { + [key: string]: components["schemas"]["InputStep"] | undefined; + }; + /** + * Type + * @description The type of workflow module. + * @default subworkflow + */ + type?: components["schemas"]["WorkflowModuleType"]; + /** + * Workflow ID + * @description The encoded ID of the workflow that will be run on this step. + * @example 0123456789ABCDEF + */ + workflow_id: string; + }; /** SuitableConverter */ SuitableConverter: { /** @@ -10911,6 +11283,47 @@ export interface components { */ values: string; }; + /** ToolStep */ + ToolStep: { + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation: string | null; + /** + * ID + * @description The identifier of the step. It matches the index order of the step inside the workflow. + */ + id: number; + /** + * Input Steps + * @description A dictionary containing information about the inputs connected to this workflow step. + */ + input_steps: { + [key: string]: components["schemas"]["InputStep"] | undefined; + }; + /** + * Tool ID + * @description The unique name of the tool associated with this step. + */ + tool_id?: string | null; + /** + * Tool Inputs + * @description TODO + */ + tool_inputs?: Record; + /** + * Tool Version + * @description The version of the tool associated with this step. + */ + tool_version?: string | null; + /** + * Type + * @description The type of workflow module. + * @default tool + */ + type?: components["schemas"]["WorkflowModuleType"]; + }; /** Tour */ Tour: { /** @@ -11706,6 +12119,25 @@ export interface components { * @default [] */ VisualizationSummaryList: components["schemas"]["VisualizationSummary"][]; + /** WorkflowInput */ + WorkflowInput: { + /** + * Label + * @description Label of the input. + */ + label: string; + /** + * UUID + * Format: uuid4 + * @description Universal unique identifier of the input. + */ + uuid: string; + /** + * Value + * @description TODO + */ + value: string; + }; /** WorkflowInvocationCollectionView */ WorkflowInvocationCollectionView: { /** @@ -11890,6 +12322,18 @@ export interface components { [key: string]: number | undefined; }; }; + /** + * WorkflowModuleType + * @description Available types of modules that represent a step in a Workflow. + * @enum {string} + */ + WorkflowModuleType: + | "data_input" + | "data_collection_input" + | "parameter_input" + | "subworkflow" + | "tool" + | "pause"; /** WriteInvocationStoreToPayload */ WriteInvocationStoreToPayload: { /** @@ -12008,6 +12452,52 @@ export interface components { */ namespace: string; }; + /** Organization */ + galaxy__schema__drs__Organization: { + /** + * Name + * @description Name of the organization responsible for the service + */ + name: string; + /** + * Url + * Format: uri + * @description URL of the website of the organization (RFC 3986 format) + */ + url: string; + }; + /** Organization */ + galaxy__schema__schema__Organization: { + /** Address */ + address?: string | null; + /** Alternate Name */ + alternateName?: string | null; + /** + * Class + * @default Organization + */ + class?: string; + /** Email */ + email?: string | null; + /** Fax Number */ + faxNumber?: string | null; + /** + * Identifier + * @description Identifier (typically an orcid.org ID) + */ + identifier?: string | null; + /** Image URL */ + image?: string | null; + /** + * Name + * @description The name of the creator. + */ + name: string; + /** Telephone */ + telephone?: string | null; + /** URL */ + url?: string | null; + }; }; responses: never; parameters: never; @@ -22264,7 +22754,7 @@ export interface operations { }; }; }; - show_workflow_api_workflows__encoded_workflow_id__get: { + show_workflow_api_workflows__workflow_id__get: { /** Displays information needed to run a workflow. */ parameters: { /** @description Use the legacy workflow format. */ @@ -22280,14 +22770,14 @@ export interface operations { }; /** @description The encoded database identifier of the Stored Workflow. */ path: { - encoded_workflow_id: string; + workflow_id: string; }; }; responses: { /** @description Successful Response */ 200: { content: { - "application/json": Record; + "application/json": components["schemas"]["StoredWorkflowDetailed"]; }; }; /** @description Validation Error */ From a8ee9c51fdd9d5bcb52209ec1b210c444ae77dbf Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Thu, 22 Feb 2024 11:14:33 +0100 Subject: [PATCH 43/57] Create pydantic model for get_tool_predictions return --- lib/galaxy/schema/workflows.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/galaxy/schema/workflows.py b/lib/galaxy/schema/workflows.py index 0c005387e4e8..e00220f876dd 100644 --- a/lib/galaxy/schema/workflows.py +++ b/lib/galaxy/schema/workflows.py @@ -96,6 +96,19 @@ class GetToolPredictionsPayload(Model): ) +class ToolPredictionsSummary(Model): + current_tool: Optional[Any] = Field( + None, + title="Current Tools", + description="A comma separated sequence of the current tool ids", + ) + predicted_data: Optional[Any] = Field( + None, + title="Recommended Tools", + description="List of predictions", + ) + + class InvokeWorkflowPayload(GetTargetHistoryPayload): # TODO - Are the descriptions correct? instance: Optional[bool] = Field( From c01ff2c386bcdef0fb3f9446ddcac0170780ac5c Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Thu, 22 Feb 2024 11:16:00 +0100 Subject: [PATCH 44/57] Make field optional in payload model for get_tool_predicitions operation --- lib/galaxy/schema/workflows.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/schema/workflows.py b/lib/galaxy/schema/workflows.py index e00220f876dd..62265a8a592f 100644 --- a/lib/galaxy/schema/workflows.py +++ b/lib/galaxy/schema/workflows.py @@ -84,8 +84,8 @@ class GetTargetHistoryPayload(Model): class GetToolPredictionsPayload(Model): - tool_sequence: Any = Field( - ..., + tool_sequence: Optional[Any] = Field( + None, title="Tool Sequence", description="comma separated sequence of tool ids", ) From e9d28d639a44f96e505772ef5a19e10faac0951c Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Thu, 22 Feb 2024 11:16:29 +0100 Subject: [PATCH 45/57] Add pydantic model to return of get_tool_predicitions operation --- lib/galaxy/webapps/galaxy/api/workflows.py | 3 ++- lib/galaxy/webapps/galaxy/services/workflows.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 8e6d90dbf555..36cf737f208f 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -84,6 +84,7 @@ GetToolPredictionsPayload, InvokeWorkflowPayload, StoredWorkflowDetailed, + ToolPredictionsSummary, ) from galaxy.structured_app import StructuredApp from galaxy.tool_shed.galaxy_install.install_manager import InstallRepositoryManager @@ -936,7 +937,7 @@ def get_tool_predictions( self, payload: GetToolPredictionsBody, trans: ProvidesUserContext = DependsOnTrans, - ): + ) -> Union[dict, ToolPredictionsSummary]: return self.service.get_tool_predictions(trans, payload.model_dump(exclude_unset=True)) @router.get( diff --git a/lib/galaxy/webapps/galaxy/services/workflows.py b/lib/galaxy/webapps/galaxy/services/workflows.py index b75f8916817a..6a229f90f501 100644 --- a/lib/galaxy/webapps/galaxy/services/workflows.py +++ b/lib/galaxy/webapps/galaxy/services/workflows.py @@ -31,6 +31,7 @@ from galaxy.schema.workflows import ( # GetToolPredictionsPayload, InvokeWorkflowPayload, StoredWorkflowDetailed, + ToolPredictionsSummary, ) from galaxy.tools import recommendations from galaxy.util.tool_shed.tool_shed_registry import Registry @@ -74,7 +75,7 @@ def get_tool_predictions( tool_sequence, recommended_tools = self.tool_recommendations.get_predictions( trans, tool_sequence, remote_model_url ) - return {"current_tool": tool_sequence, "predicted_data": recommended_tools} + return ToolPredictionsSummary(current_tool=tool_sequence, recommended_tools=recommended_tools) def index( self, From 6ff8f45e94c216eaf180d4a2dcad84b96fd67206 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Thu, 22 Feb 2024 11:17:35 +0100 Subject: [PATCH 46/57] Regenerate the client schema --- client/src/api/schema/schema.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index a0572fe56d59..1db36b2fc626 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -5202,7 +5202,7 @@ export interface components { * Tool Sequence * @description comma separated sequence of tool ids */ - tool_sequence: Record; + tool_sequence?: Record | null; }; /** * GroupCreatePayload @@ -11283,6 +11283,19 @@ export interface components { */ values: string; }; + /** ToolPredictionsSummary */ + ToolPredictionsSummary: { + /** + * Current Tools + * @description A comma separated sequence of the current tool ids + */ + current_tool?: Record | null; + /** + * Recommended Tools + * @description List of predictions + */ + predicted_data?: Record | null; + }; /** ToolStep */ ToolStep: { /** @@ -22710,7 +22723,7 @@ export interface operations { /** @description Successful Response */ 200: { content: { - "application/json": Record; + "application/json": Record | components["schemas"]["ToolPredictionsSummary"]; }; }; /** @description Validation Error */ From 65da13be0aaeb03c6ba779ddc34a592bcdb277ee Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Fri, 23 Feb 2024 09:20:08 +0100 Subject: [PATCH 47/57] Move get_tool_predictions operation to legacy route --- lib/galaxy/webapps/galaxy/api/workflows.py | 42 +++++++++---------- lib/galaxy/webapps/galaxy/buildapp.py | 6 +++ .../webapps/galaxy/services/workflows.py | 19 +-------- 3 files changed, 27 insertions(+), 40 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 36cf737f208f..c5e1d8e00af5 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -81,10 +81,8 @@ WorkflowSortByEnum, ) from galaxy.schema.workflows import ( - GetToolPredictionsPayload, InvokeWorkflowPayload, StoredWorkflowDetailed, - ToolPredictionsSummary, ) from galaxy.structured_app import StructuredApp from galaxy.tool_shed.galaxy_install.install_manager import InstallRepositoryManager @@ -559,6 +557,26 @@ def build_module(self, trans: GalaxyWebTransaction, payload=None): step_dict["tool_version"] = module.get_version() return step_dict + @expose_api + def get_tool_predictions(self, trans: ProvidesUserContext, payload, **kwd): + """ + POST /api/workflows/get_tool_predictions + Fetch predicted tools for a workflow + :type payload: dict + :param payload: + a dictionary containing two parameters + 'tool_sequence' - comma separated sequence of tool ids + 'remote_model_url' - (optional) path to the deep learning model + """ + remote_model_url = payload.get("remote_model_url", trans.app.config.tool_recommendation_model_path) + tool_sequence = payload.get("tool_sequence", "") + if "tool_sequence" not in payload or remote_model_url is None: + return + tool_sequence, recommended_tools = self.tool_recommendations.get_predictions( + trans, tool_sequence, remote_model_url + ) + return {"current_tool": tool_sequence, "predicted_data": recommended_tools} + # # -- Helper methods -- # @@ -874,15 +892,6 @@ def __get_stored_workflow(self, trans, workflow_id, **kwd): ), ] -GetToolPredictionsBody = Annotated[ - GetToolPredictionsPayload, - Body( - default=..., - title="Get tool predictions", - description="The values to get tool predictions for a workflow.", - ), -] - @router.cbv class FastAPIWorkflows: @@ -929,17 +938,6 @@ def index( response.headers["total_matches"] = str(total_matches) return workflows - @router.post( - "/api/workflows/get_tool_predictions", - summary="Fetch predicted tools for a workflow", - ) - def get_tool_predictions( - self, - payload: GetToolPredictionsBody, - trans: ProvidesUserContext = DependsOnTrans, - ) -> Union[dict, ToolPredictionsSummary]: - return self.service.get_tool_predictions(trans, payload.model_dump(exclude_unset=True)) - @router.get( "/api/workflows/{workflow_id}/sharing", summary="Get the current sharing status of the given item.", diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py index 704b8f8c3e28..bad2c03b7ea3 100644 --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -580,6 +580,12 @@ def populate_api_routes(webapp, app): controller="container_resolution", conditions=dict(method=["POST"]), ) + webapp.mapper.connect( + "/api/workflows/get_tool_predictions", + action="get_tool_predictions", + controller="workflows", + conditions=dict(method=["POST"]), + ) webapp.mapper.resource("visualization", "visualizations", path_prefix="/api") webapp.mapper.resource("plugins", "plugins", path_prefix="/api") diff --git a/lib/galaxy/webapps/galaxy/services/workflows.py b/lib/galaxy/webapps/galaxy/services/workflows.py index 6a229f90f501..472e7e2b4a62 100644 --- a/lib/galaxy/webapps/galaxy/services/workflows.py +++ b/lib/galaxy/webapps/galaxy/services/workflows.py @@ -28,12 +28,10 @@ InvocationsStateCounts, WorkflowIndexQueryPayload, ) -from galaxy.schema.workflows import ( # GetToolPredictionsPayload, +from galaxy.schema.workflows import ( InvokeWorkflowPayload, StoredWorkflowDetailed, - ToolPredictionsSummary, ) -from galaxy.tools import recommendations from galaxy.util.tool_shed.tool_shed_registry import Registry from galaxy.webapps.galaxy.services.base import ServiceBase from galaxy.webapps.galaxy.services.sharable import ShareableService @@ -61,21 +59,6 @@ def __init__( self._serializer = serializer self.shareable_service = ShareableService(workflows_manager, serializer, notification_manager) self._tool_shed_registry = tool_shed_registry - self.tool_recommendations = recommendations.ToolRecommendations() - - def get_tool_predictions( - self, - trans: ProvidesUserContext, - payload, - ): - remote_model_url = payload.get("remote_model_url", trans.app.config.tool_recommendation_model_path) - tool_sequence = payload.get("tool_sequence", "") - if "tool_sequence" not in payload or remote_model_url is None: - return {} - tool_sequence, recommended_tools = self.tool_recommendations.get_predictions( - trans, tool_sequence, remote_model_url - ) - return ToolPredictionsSummary(current_tool=tool_sequence, recommended_tools=recommended_tools) def index( self, From 78725fb9311a3458782941a0a76aa80dcb9c35a6 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Fri, 23 Feb 2024 09:20:44 +0100 Subject: [PATCH 48/57] Remove models for get_tool_predictions --- lib/galaxy/schema/workflows.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/lib/galaxy/schema/workflows.py b/lib/galaxy/schema/workflows.py index 62265a8a592f..94327410fbc6 100644 --- a/lib/galaxy/schema/workflows.py +++ b/lib/galaxy/schema/workflows.py @@ -83,32 +83,6 @@ class GetTargetHistoryPayload(Model): ) -class GetToolPredictionsPayload(Model): - tool_sequence: Optional[Any] = Field( - None, - title="Tool Sequence", - description="comma separated sequence of tool ids", - ) - remote_model_url: Optional[Any] = Field( - None, - title="Remote Model URL", - description="Path to the deep learning model", - ) - - -class ToolPredictionsSummary(Model): - current_tool: Optional[Any] = Field( - None, - title="Current Tools", - description="A comma separated sequence of the current tool ids", - ) - predicted_data: Optional[Any] = Field( - None, - title="Recommended Tools", - description="List of predictions", - ) - - class InvokeWorkflowPayload(GetTargetHistoryPayload): # TODO - Are the descriptions correct? instance: Optional[bool] = Field( From 80110bd1a420daec2593c4808a80df4af8c95e24 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Fri, 23 Feb 2024 09:21:09 +0100 Subject: [PATCH 49/57] Remove comments --- lib/galaxy/schema/workflows.py | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/lib/galaxy/schema/workflows.py b/lib/galaxy/schema/workflows.py index 94327410fbc6..7d5fecb024a0 100644 --- a/lib/galaxy/schema/workflows.py +++ b/lib/galaxy/schema/workflows.py @@ -28,38 +28,6 @@ WorkflowInput, ) -# class WorkflowExtractionParams(Model): -# from_history_id: str = Field(..., title="From History ID", description="Id of history to extract a workflow from.") -# job_ids: Optional[str] = Field( -# None, -# title="Job IDs", -# description="List of jobs to include when extracting a workflow from history", -# ) - -# @field_validator("dataset_ids", mode="before", check_fields=False) -# @classmethod -# def inputs_string_to_json(cls, v): -# if isinstance(v, str): -# return json.loads(v) -# return v - -# dataset_ids: Optional[str] = Field( -# None, -# title="Dataset IDs", -# description="List of HDA 'hid's corresponding to workflow inputs when extracting a workflow from history", -# alias="ds_map", -# ) -# dataset_collection_ids: Optional[str] = Field( -# None, -# title="Dataset Collection IDs", -# description="List of HDCA 'hid's corresponding to workflow inputs when extracting a workflow from history", -# ) -# workflow_name: Optional[str] = Field( -# None, -# title="Workflow Name", -# description="Name of the workflow to create when extracting a workflow from history", -# ) - class GetTargetHistoryPayload(Model): # TODO - Are the descriptions correct? From 362d24574fda7c1c222cb5e2fcaa29443ed6a786 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Fri, 23 Feb 2024 09:22:42 +0100 Subject: [PATCH 50/57] Regenerate the client schema --- client/src/api/schema/schema.ts | 58 --------------------------------- 1 file changed, 58 deletions(-) diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 1db36b2fc626..cb658ae9bf08 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -1799,10 +1799,6 @@ export interface paths { */ get: operations["index_api_workflows_get"]; }; - "/api/workflows/get_tool_predictions": { - /** Fetch predicted tools for a workflow */ - post: operations["get_tool_predictions_api_workflows_get_tool_predictions_post"]; - }; "/api/workflows/menu": { /** Get workflows present in the tools panel. */ get: operations["get_workflow_menu_api_workflows_menu_get"]; @@ -5191,19 +5187,6 @@ export interface components { /** Tags */ tags?: string[] | null; }; - /** GetToolPredictionsPayload */ - GetToolPredictionsPayload: { - /** - * Remote Model URL - * @description Path to the deep learning model - */ - remote_model_url?: Record | null; - /** - * Tool Sequence - * @description comma separated sequence of tool ids - */ - tool_sequence?: Record | null; - }; /** * GroupCreatePayload * @description Payload schema for creating a group. @@ -11283,19 +11266,6 @@ export interface components { */ values: string; }; - /** ToolPredictionsSummary */ - ToolPredictionsSummary: { - /** - * Current Tools - * @description A comma separated sequence of the current tool ids - */ - current_tool?: Record | null; - /** - * Recommended Tools - * @description List of predictions - */ - predicted_data?: Record | null; - }; /** ToolStep */ ToolStep: { /** @@ -22706,34 +22676,6 @@ export interface operations { }; }; }; - get_tool_predictions_api_workflows_get_tool_predictions_post: { - /** Fetch predicted tools for a workflow */ - parameters?: { - /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ - header?: { - "run-as"?: string | null; - }; - }; - requestBody: { - content: { - "application/json": components["schemas"]["GetToolPredictionsPayload"]; - }; - }; - responses: { - /** @description Successful Response */ - 200: { - content: { - "application/json": Record | components["schemas"]["ToolPredictionsSummary"]; - }; - }; - /** @description Validation Error */ - 422: { - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; get_workflow_menu_api_workflows_menu_get: { /** Get workflows present in the tools panel. */ parameters?: { From b82ed24968b35283c9ee4ffe3735f5da4701eb0b Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Fri, 23 Feb 2024 19:31:30 +0100 Subject: [PATCH 51/57] Move manager logic of refactor operation to service layer --- lib/galaxy/webapps/galaxy/api/workflows.py | 9 ++------- lib/galaxy/webapps/galaxy/services/workflows.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index c5e1d8e00af5..bf755e43f009 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -47,9 +47,7 @@ MissingToolsException, RefactorRequest, RefactorResponse, - WorkflowContentsManager, WorkflowCreateOptions, - WorkflowsManager, WorkflowUpdateOptions, ) from galaxy.model.base import transaction @@ -778,7 +776,7 @@ def __get_stored_workflow(self, trans, workflow_id, **kwd): Path( ..., title="Workflow ID", - description="The database identifier - UUID or encoded - of the Workflow..", + description="The database identifier - UUID or encoded - of the Workflow.", ), ] @@ -896,8 +894,6 @@ def __get_stored_workflow(self, trans, workflow_id, **kwd): @router.cbv class FastAPIWorkflows: service: WorkflowsService = depends(WorkflowsService) - manager: WorkflowsManager = depends(WorkflowsManager) - contents_manager: WorkflowContentsManager = depends(WorkflowContentsManager) @router.get( "/api/workflows", @@ -985,8 +981,7 @@ def refactor( instance: InstanceQueryParam = False, trans: ProvidesUserContext = DependsOnTrans, ) -> RefactorResponse: - stored_workflow = self.manager.get_stored_workflow(trans, workflow_id, by_stored_id=not instance) - return self.contents_manager.refactor(trans, stored_workflow, payload) + return self.service.refactor(trans, workflow_id, payload, instance or False) @router.put( "/api/workflows/{workflow_id}/publish", diff --git a/lib/galaxy/webapps/galaxy/services/workflows.py b/lib/galaxy/webapps/galaxy/services/workflows.py index 472e7e2b4a62..411d04dd3a95 100644 --- a/lib/galaxy/webapps/galaxy/services/workflows.py +++ b/lib/galaxy/webapps/galaxy/services/workflows.py @@ -17,6 +17,7 @@ from galaxy.managers.context import ProvidesUserContext from galaxy.managers.notification import NotificationManager from galaxy.managers.workflows import ( + RefactorResponse, WorkflowContentsManager, WorkflowSerializer, WorkflowsManager, @@ -217,6 +218,16 @@ def get_workflow_menu(self, trans, payload): ) return {"ids_in_menu": ids_in_menu, "workflows": workflows} + def refactor( + self, + trans, + workflow_id, + payload, + instance: bool, + ) -> RefactorResponse: + stored_workflow = self._workflows_manager.get_stored_workflow(trans, workflow_id, by_stored_id=not instance) + return self._workflow_contents_manager.refactor(trans, stored_workflow, payload) + def show_workflow(self, trans, workflow_id, instance, legacy, version) -> StoredWorkflowDetailed: stored_workflow = self._workflows_manager.get_stored_workflow(trans, workflow_id, by_stored_id=not instance) if stored_workflow.importable is False and stored_workflow.user != trans.user and not trans.user_is_admin: From 393ffc6423c21c7eb82c767ed30e2bcd799686c4 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Fri, 23 Feb 2024 19:32:07 +0100 Subject: [PATCH 52/57] Refine desctiptions --- lib/galaxy/schema/workflows.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/schema/workflows.py b/lib/galaxy/schema/workflows.py index 7d5fecb024a0..0cf1a642f49d 100644 --- a/lib/galaxy/schema/workflows.py +++ b/lib/galaxy/schema/workflows.py @@ -198,16 +198,15 @@ class StoredWorkflowDetailed(StoredWorkflowSummary): SubworkflowStep, ], ] = Field({}, title="Steps", description="A dictionary with information about all the steps of the workflow.") - # TODO - Check descriptions of newly added fields importable: Optional[bool] = Field( ..., title="Importable", - description="Indicates if the workflow is importable by ?.", + description="Indicates if the workflow is importable by the current user.", ) email_hash: Optional[str] = Field( ..., title="Email Hash", - description="The hash of the email of ?", + description="The hash of the email of the creator of this workflow", ) slug: Optional[str] = Field( ..., From 9345085d3131843d89f37e36f513d722582dbc13 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Fri, 23 Feb 2024 19:32:18 +0100 Subject: [PATCH 53/57] Regenerate the client schema --- client/src/api/schema/schema.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index cb658ae9bf08..2f10be540fd4 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -10977,7 +10977,7 @@ export interface components { deleted: boolean; /** * Email Hash - * @description The hash of the email of ? + * @description The hash of the email of the creator of this workflow */ email_hash: string | null; /** @@ -10992,7 +10992,7 @@ export interface components { id: string; /** * Importable - * @description Indicates if the workflow is importable by ?. + * @description Indicates if the workflow is importable by the current user. */ importable: boolean | null; /** @@ -22919,7 +22919,7 @@ export interface operations { header?: { "run-as"?: string | null; }; - /** @description The database identifier - UUID or encoded - of the Workflow.. */ + /** @description The database identifier - UUID or encoded - of the Workflow. */ path: { workflow_id: string | string | string; }; @@ -23659,7 +23659,7 @@ export interface operations { header?: { "run-as"?: string | null; }; - /** @description The database identifier - UUID or encoded - of the Workflow.. */ + /** @description The database identifier - UUID or encoded - of the Workflow. */ path: { workflow_id: string | string | string; }; From 5400f7478676bb0bf6cb96dc9696a44c0a540cd3 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Fri, 23 Feb 2024 21:04:15 +0100 Subject: [PATCH 54/57] Use galaxy exceptions instead of FastAPI HTTPError --- lib/galaxy/webapps/galaxy/services/workflows.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/services/workflows.py b/lib/galaxy/webapps/galaxy/services/workflows.py index 411d04dd3a95..52e35d96c8fe 100644 --- a/lib/galaxy/webapps/galaxy/services/workflows.py +++ b/lib/galaxy/webapps/galaxy/services/workflows.py @@ -8,8 +8,6 @@ Union, ) -from fastapi import HTTPException - from galaxy import ( exceptions, web, @@ -133,7 +131,7 @@ def invoke_workflow( run_configs = build_workflow_run_configs(trans, workflow, payload.model_dump(exclude_unset=True)) is_batch = payload.batch if not is_batch and len(run_configs) != 1: - raise HTTPException(status_code=400, detail="Must specify 'batch' to use batch parameters.") + raise exceptions.RequestParameterInvalidException("Must specify 'batch' to use batch parameters.") require_exact_tool_versions = payload.require_exact_tool_versions tools = self._workflow_contents_manager.get_all_tools(workflow) @@ -152,16 +150,7 @@ def invoke_workflow( ) else: missing_tools_message += ", ".join([tool["tool_id"] for tool in missing_tools]) - raise HTTPException(status_code=400, detail=missing_tools_message) - # if missing_tools: - # missing_tools_message = "Workflow was not invoked; the following required tools are not installed: " - # if require_exact_tool_versions: - # missing_tools_message += ", ".join( - # [f"{tool['tool_id']} (version {tool['tool_version']})" for tool in missing_tools] - # ) - # else: - # missing_tools_message += ", ".join([tool["tool_id"] for tool in missing_tools]) - # raise exceptions.MessageException(missing_tools_message) + raise exceptions.MessageException(missing_tools_message) invocations = [] for run_config in run_configs: From 6c1f30eee2a4df4b0929d4a1c228769324c6bdcb Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Sat, 24 Feb 2024 08:30:39 +0100 Subject: [PATCH 55/57] Access the error message of invoke endpoint correctly --- test/integration/test_workflow_invocation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/test_workflow_invocation.py b/test/integration/test_workflow_invocation.py index 70d0d541e83b..72276bf77979 100644 --- a/test/integration/test_workflow_invocation.py +++ b/test/integration/test_workflow_invocation.py @@ -110,7 +110,7 @@ def test_run_workflow_with_missing_tool(self): ) self._assert_status_code_is(invocation_response, 400) assert ( - invocation_response.json().get("detail") + invocation_response.json().get("err_msg") == "Workflow was not invoked; the following required tools are not installed: nonexistent_tool (version 0.1), compose_text_param (version 0.0.1)" ) # should fail but return only the tool_id of non_existent tool as another version of compose_text_param is installed @@ -119,6 +119,6 @@ def test_run_workflow_with_missing_tool(self): ) self._assert_status_code_is(invocation_response, 400) assert ( - invocation_response.json().get("detail") + invocation_response.json().get("err_msg") == "Workflow was not invoked; the following required tools are not installed: nonexistent_tool" ) From bc705a273b8a536f13c112073d1bcb0632b095d6 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Mon, 26 Feb 2024 18:11:07 +0100 Subject: [PATCH 56/57] Change type of metadata source field in pydantic model for show_workflow operation to dict --- lib/galaxy/schema/workflows.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/schema/workflows.py b/lib/galaxy/schema/workflows.py index 0cf1a642f49d..598878d749c2 100644 --- a/lib/galaxy/schema/workflows.py +++ b/lib/galaxy/schema/workflows.py @@ -213,7 +213,7 @@ class StoredWorkflowDetailed(StoredWorkflowSummary): title="Slug", description="The slug of the workflow.", ) - source_metadata: Optional[str] = Field( + source_metadata: Optional[Dict[str, Any]] = Field( ..., title="Source Metadata", description="The source metadata of the workflow.", From 1aa27cbf0e5d5d1f9b2546603ac31dd2151e3ba3 Mon Sep 17 00:00:00 2001 From: heisner-tillman Date: Mon, 26 Feb 2024 18:11:19 +0100 Subject: [PATCH 57/57] Regenerate the client schema --- client/src/api/schema/schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 2f10be540fd4..a1b56a23d96a 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -11054,7 +11054,7 @@ export interface components { * Source Metadata * @description The source metadata of the workflow. */ - source_metadata: string | null; + source_metadata: Record | null; /** * Steps * @description A dictionary with information about all the steps of the workflow.