From 430eb3ef288ec362355e21c46768f22cdbdbdd0e Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Tue, 27 Feb 2024 16:27:17 -0600 Subject: [PATCH] Filter out subworkflow invocations I think users don't need to see their nested invocations from the main invocation list. If needed they can be found through the invocation steps. The only exception we're making is that we show subworkflow invocations if filtering for a specific workflow id. --- client/src/api/schema/schema.ts | 1 + client/src/components/Workflow/InvocationsList.vue | 2 ++ lib/galaxy/managers/workflows.py | 14 +++++++++++++- lib/galaxy/schema/schema.py | 1 + lib/galaxy/webapps/galaxy/api/workflows.py | 2 ++ lib/galaxy/webapps/galaxy/services/invocations.py | 1 + lib/galaxy_test/api/test_workflows.py | 14 ++++++++++++++ lib/galaxy_test/base/populators.py | 8 +++++--- 8 files changed, 39 insertions(+), 4 deletions(-) diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 89227e632ec..a37cc31b628 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -16759,6 +16759,7 @@ export interface operations { instance?: boolean | null; view?: string | null; step_details?: boolean; + include_nested_invocations?: boolean; }; /** @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?: { diff --git a/client/src/components/Workflow/InvocationsList.vue b/client/src/components/Workflow/InvocationsList.vue index 3bdc529f3e4..d3dd78d1648 100644 --- a/client/src/components/Workflow/InvocationsList.vue +++ b/client/src/components/Workflow/InvocationsList.vue @@ -200,6 +200,8 @@ export default { const extraParams = this.ownerGrid ? {} : { include_terminal: false }; if (this.storedWorkflowId) { extraParams["workflow_id"] = this.storedWorkflowId; + } else { + extraParams["include_nested_invocations"] = false; } if (this.historyId) { extraParams["history_id"] = this.historyId; diff --git a/lib/galaxy/managers/workflows.py b/lib/galaxy/managers/workflows.py index 109ade27ace..054f5f1e78a 100644 --- a/lib/galaxy/managers/workflows.py +++ b/lib/galaxy/managers/workflows.py @@ -69,6 +69,7 @@ from galaxy.model import ( Workflow, WorkflowInvocation, WorkflowInvocationStep, + WorkflowInvocationToSubworkflowInvocationAssociation, ) from galaxy.model.base import ( ensure_object_added_to_session, @@ -486,6 +487,7 @@ class WorkflowsManager(sharable.SharableModelManager, deletable.DeletableManager offset=None, sort_by=None, sort_desc=None, + include_nested_invocations=True, ) -> Tuple[Query, int]: """Get invocations owned by the current user.""" @@ -503,6 +505,16 @@ class WorkflowsManager(sharable.SharableModelManager, deletable.DeletableManager stmt = stmt.join(WorkflowInvocationStep).where(WorkflowInvocationStep.job_id == job_id) if not include_terminal: stmt = stmt.where(WorkflowInvocation.state.in_(WorkflowInvocation.non_terminal_states)) + if not include_nested_invocations: + subquery = ( + select(WorkflowInvocationToSubworkflowInvocationAssociation.id) + .where( + WorkflowInvocationToSubworkflowInvocationAssociation.subworkflow_invocation_id + == WorkflowInvocation.id + ) + .exists() + ) + stmt = stmt.where(~subquery) total_matches = get_count(trans.sa_session, stmt) @@ -2091,5 +2103,5 @@ def _get_invocation(session, eager, invocation_id): def get_count(session, statement): - stmt = select(func.count()).select_from(statement) + stmt = select(func.count()).select_from(statement.subquery()) return session.scalar(stmt) diff --git a/lib/galaxy/schema/schema.py b/lib/galaxy/schema/schema.py index f7342f60be9..54fcdcbe2b7 100644 --- a/lib/galaxy/schema/schema.py +++ b/lib/galaxy/schema/schema.py @@ -1452,6 +1452,7 @@ class InvocationIndexQueryPayload(Model): lt=1000, ) offset: Optional[int] = Field(default=0, description="Number of invocations to skip") + include_nested_invocations: bool = True PageSortByEnum = Literal["create_time", "title", "update_time", "username"] diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 7d4df8da266..b529f0ca91c 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -1324,6 +1324,7 @@ class FastAPIInvocations: instance: InvocationsInstanceQueryParam = False, view: SerializationViewQueryParam = None, step_details: StepDetailQueryParam = False, + include_nested_invocations: bool = True, trans: ProvidesUserContext = DependsOnTrans, ) -> List[WorkflowInvocationResponse]: invocation_payload = InvocationIndexPayload( @@ -1337,6 +1338,7 @@ class FastAPIInvocations: limit=limit, offset=offset, instance=instance, + include_nested_invocations=include_nested_invocations, ) serialization_params = InvocationSerializationParams( view=view, diff --git a/lib/galaxy/webapps/galaxy/services/invocations.py b/lib/galaxy/webapps/galaxy/services/invocations.py index 79912285c2a..3e9f3385464 100644 --- a/lib/galaxy/webapps/galaxy/services/invocations.py +++ b/lib/galaxy/webapps/galaxy/services/invocations.py @@ -126,6 +126,7 @@ class InvocationsService(ServiceBase, ConsumesModelStores): offset=invocation_payload.offset, sort_by=invocation_payload.sort_by, sort_desc=invocation_payload.sort_desc, + include_nested_invocations=invocation_payload.include_nested_invocations, ) invocation_dict = self.serialize_workflow_invocations(invocations, serialization_params) return invocation_dict, total_matches diff --git a/lib/galaxy_test/api/test_workflows.py b/lib/galaxy_test/api/test_workflows.py index aa0d83b3c9e..7bfd3e75837 100644 --- a/lib/galaxy_test/api/test_workflows.py +++ b/lib/galaxy_test/api/test_workflows.py @@ -7145,6 +7145,20 @@ input_c: usage_details_response = self._get(f"workflows/{workflow_id}/usage/{invocation_id}") self._assert_status_code_is(usage_details_response, 403) + def test_invocation_filtering_exclude_subworkflow(self): + with self.dataset_populator.test_history() as history_id: + self._run_workflow( + WORKFLOW_NESTED_SIMPLE, + test_data=""" +outer_input: + value: 1.bed + type: File +""", + history_id=history_id, + ) + assert len(self.workflow_populator.history_invocations(history_id)) == 2 + assert len(self.workflow_populator.history_invocations(history_id, include_nested_invocations=False)) == 1 + def test_workflow_publishing(self): workflow_id = self.workflow_populator.simple_workflow("dummy") response = self._show_workflow(workflow_id) diff --git a/lib/galaxy_test/base/populators.py b/lib/galaxy_test/base/populators.py index 3bad7b7f034..4d8836bdc6f 100644 --- a/lib/galaxy_test/base/populators.py +++ b/lib/galaxy_test/base/populators.py @@ -1701,7 +1701,7 @@ class BaseWorkflowPopulator(BasePopulator): return wait_on_state(workflow_state, desc="workflow invocation state", timeout=timeout, assert_ok=assert_ok) - def workflow_invocations(self, workflow_id: str) -> List[Dict[str, Any]]: + def workflow_invocations(self, workflow_id: str, include_nested_invocations=True) -> List[Dict[str, Any]]: response = self._get(f"workflows/{workflow_id}/invocations") api_asserts.assert_status_code_is(response, 200) return response.json() @@ -1711,8 +1711,10 @@ class BaseWorkflowPopulator(BasePopulator): api_asserts.assert_status_code_is(response, 200) return response.json() - def history_invocations(self, history_id: str) -> List[Dict[str, Any]]: - history_invocations_response = self._get("invocations", {"history_id": history_id}) + def history_invocations(self, history_id: str, include_nested_invocations: bool = True) -> List[Dict[str, Any]]: + history_invocations_response = self._get( + "invocations", {"history_id": history_id, "include_nested_invocations": include_nested_invocations} + ) api_asserts.assert_status_code_is(history_invocations_response, 200) return history_invocations_response.json()