diff --git a/lib/galaxy/managers/workflows.py b/lib/galaxy/managers/workflows.py index af92bf6bae5..e98d12fc191 100644 --- a/lib/galaxy/managers/workflows.py +++ b/lib/galaxy/managers/workflows.py @@ -42,7 +42,10 @@ from galaxy import ( util, ) from galaxy.job_execution.actions.post import ActionBox -from galaxy.managers import sharable +from galaxy.managers import ( + deletable, + sharable, +) from galaxy.managers.base import decode_id from galaxy.managers.context import ProvidesUserContext from galaxy.managers.executables import artifact_class @@ -107,7 +110,7 @@ INDEX_SEARCH_FILTERS = { } -class WorkflowsManager(sharable.SharableModelManager): +class WorkflowsManager(sharable.SharableModelManager, deletable.DeletableManagerMixin): """Handle CRUD type operations related to workflows. More interesting stuff regarding workflow execution, step sorting, etc... can be found in the galaxy.workflow module. diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 00a823de8b9..38f2486a4ee 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -457,28 +457,6 @@ class WorkflowsAPIController( else: return format_return_as_json(ret_dict, pretty=True) - @expose_api - def delete(self, trans: ProvidesUserContext, id, **kwd): - """ - DELETE /api/workflows/{encoded_workflow_id} - Deletes a specified workflow - Author: rpark - - copied from galaxy.web.controllers.workflows.py (delete) - """ - stored_workflow = self.__get_stored_workflow(trans, id, **kwd) - - # check to see if user has permissions to selected workflow - if stored_workflow.user != trans.user and not trans.user_is_admin: - raise exceptions.InsufficientPermissionsException() - - # Mark a workflow as deleted - stored_workflow.deleted = True - trans.sa_session.flush() - - # TODO: Unsure of response message to let api know that a workflow was successfully deleted - return f"Workflow '{stored_workflow.name}' successfully deleted" - @expose_api def import_new_workflow_deprecated(self, trans: GalaxyWebTransaction, payload, **kwd): """ @@ -1423,6 +1401,30 @@ class FastAPIWorkflows: ) return rval + @router.delete( + "/api/workflows/{workflow_id}", + summary="Add the deleted flag to a workflow.", + ) + def delete_workflow( + self, + trans: ProvidesUserContext = DependsOnTrans, + workflow_id: DecodedDatabaseIdField = StoredWorkflowIDPathParam, + ): + self.service.delete(trans, workflow_id) + return Response(status_code=status.HTTP_204_NO_CONTENT) + + @router.post( + "/api/workflows/{workflow_id}/undelete", + summary="Remove the deleted flag from a workflow.", + ) + def undelete_workflow( + self, + trans: ProvidesUserContext = DependsOnTrans, + workflow_id: DecodedDatabaseIdField = StoredWorkflowIDPathParam, + ): + self.service.undelete(trans, workflow_id) + return Response(status_code=status.HTTP_204_NO_CONTENT) + # TODO: remove this endpoint after 23.1 release @router.get( "/api/invocations/{invocation_id}/biocompute", diff --git a/lib/galaxy/webapps/galaxy/services/workflows.py b/lib/galaxy/webapps/galaxy/services/workflows.py index 8758110155e..c96f654de33 100644 --- a/lib/galaxy/webapps/galaxy/services/workflows.py +++ b/lib/galaxy/webapps/galaxy/services/workflows.py @@ -99,6 +99,16 @@ class WorkflowsService(ServiceBase): return workflows, total_matches return rval, total_matches + 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) + self._workflows_manager.delete(workflow_to_delete) + + def undelete(self, trans, workflow_id): + workflow_to_undelete = self._workflows_manager.get_stored_workflow(trans, workflow_id) + self._workflows_manager.check_security(trans, workflow_to_undelete) + self._workflows_manager.undelete(workflow_to_undelete) + def __get_full_shed_url(self, url): for shed_url in self._tool_shed_registry.tool_sheds.values(): if url in shed_url: diff --git a/lib/galaxy_test/api/test_workflows.py b/lib/galaxy_test/api/test_workflows.py index 725b9480c27..83953be8836 100644 --- a/lib/galaxy_test/api/test_workflows.py +++ b/lib/galaxy_test/api/test_workflows.py @@ -20,6 +20,7 @@ import yaml from requests import ( delete, get, + post, put, ) @@ -372,7 +373,7 @@ class TestWorkflowsApi(BaseWorkflowsApiTestCase, ChangeDatatypeTestCase): self._assert_user_has_workflow_with_name(workflow_name) workflow_url = self._api_url(f"workflows/{workflow_id}", use_key=True) delete_response = delete(workflow_url) - self._assert_status_code_is(delete_response, 200) + self._assert_status_code_is(delete_response, 204) # Make sure workflow is no longer in index by default. assert workflow_name not in self._workflow_names() @@ -383,6 +384,26 @@ class TestWorkflowsApi(BaseWorkflowsApiTestCase, ChangeDatatypeTestCase): delete_response = delete(workflow_url) self._assert_status_code_is(delete_response, 403) + def test_undelete(self): + workflow_id = self.workflow_populator.simple_workflow("test_undelete") + workflow_name = "test_undelete" + self._assert_user_has_workflow_with_name(workflow_name) + workflow_delete_url = self._api_url(f"workflows/{workflow_id}", use_key=True) + delete(workflow_delete_url) + workflow_undelete_url = self._api_url(f"workflows/{workflow_id}/undelete", use_key=True) + undelete_response = post(workflow_undelete_url) + self._assert_status_code_is(undelete_response, 204) + assert workflow_name in self._workflow_names() + + def test_other_cannot_undelete(self): + workflow_id = self.workflow_populator.simple_workflow("test_other_undelete") + workflow_delete_url = self._api_url(f"workflows/{workflow_id}", use_key=True) + delete(workflow_delete_url) + with self._different_user(): + workflow_undelete_url = self._api_url(f"workflows/{workflow_id}/undelete", use_key=True) + undelete_response = post(workflow_undelete_url) + self._assert_status_code_is(undelete_response, 403) + def test_index(self): index_response = self._get("workflows") self._assert_status_code_is(index_response, 200) @@ -394,7 +415,7 @@ class TestWorkflowsApi(BaseWorkflowsApiTestCase, ChangeDatatypeTestCase): assert [w for w in workflow_index if w["id"] == workflow_id] workflow_url = self._api_url(f"workflows/{workflow_id}", use_key=True) delete_response = delete(workflow_url) - self._assert_status_code_is(delete_response, 200) + self._assert_status_code_is(delete_response, 204) workflow_index = self._get("workflows").json() assert not [w for w in workflow_index if w["id"] == workflow_id] workflow_index = self._get("workflows?show_deleted=true").json()