From 44e313fa6c3ed517ffcb6ce86e64c5be1a6b18e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20G=C3=B6bel?= <dgoebel@techfak.uni-bielefeld.de>
Date: Mon, 6 Mar 2023 16:14:56 +0100
Subject: [PATCH] Filter workflow versions when listing workflows

#18
---
 app/api/endpoints/workflow.py     | 25 +++++++++++++++----------
 app/crud/crud_workflow.py         | 25 ++++++++++++++++---------
 app/crud/crud_workflow_version.py |  4 +++-
 app/tests/api/test_workflow.py    |  8 ++++++--
 app/tests/crud/test_workflow.py   |  2 +-
 5 files changed, 41 insertions(+), 23 deletions(-)

diff --git a/app/api/endpoints/workflow.py b/app/api/endpoints/workflow.py
index 30499c8..8857eb7 100644
--- a/app/api/endpoints/workflow.py
+++ b/app/api/endpoints/workflow.py
@@ -38,8 +38,10 @@ async def list_workflows(
         max_length=30,
         description="Filter workflows by a substring in their name.",
     ),
-    filter_unpublished: bool = Query(
-        False, description="Filter Workflows with unpublished versions. Permission 'Workflow:list_filter' required"
+    version_status: list[WorkflowVersion.Status]
+    | None = Query(
+        None,
+        description=f"Which versions of the workflow to include in the response. Permission 'workflow:list_filter required'. Default {WorkflowVersion.Status.PUBLISHED.name} and {WorkflowVersion.Status.DEPRECATED.name}.",  # noqa: E501
     ),
     db: AsyncSession = Depends(get_db),
     authorization: Callable[[str], Awaitable[Any]] = Depends(workflow_authorization),
@@ -54,8 +56,8 @@ async def list_workflows(
         Async database session to perform query on. Dependency Injection.
     name_substring : string | None, default None
         Filter workflows by a substring in their name. Query Parameter.
-    filter_unpublished : bool, default False
-        Filter workflows that have an unpublished version
+    version_status : list[clowmdb.models.WorkflowVersionReduced.Status] | None, default None
+        Status of Workflow versions to filter for to fetch. Query Parameter.
     authorization : Callable[[str], Awaitable[Any]]
         Async function to ask the auth service for authorization. Dependency Injection.
 
@@ -64,13 +66,16 @@ async def list_workflows(
     workflows : list[app.schemas.workflow.WorkflowOut]
         Workflows in the system
     """
-    rbac_operation = "list_filter" if filter_unpublished else "list"
+    rbac_operation = "list_filter" if version_status is not None else "list"
     await authorization(rbac_operation)
-    workflows = await CRUDWorkflow.list(db, name_substring=name_substring)
-    return [
-        WorkflowOut.from_db_workflow(workflow, versions=await CRUDWorkflowVersion.list(db, wid=workflow.workflow_id))
-        for workflow in workflows
-    ]
+    workflows: list[Workflow] = await CRUDWorkflow.list(
+        db,
+        name_substring=name_substring,
+        version_status=[WorkflowVersion.Status.PUBLISHED, WorkflowVersion.Status.DEPRECATED]
+        if version_status is None
+        else version_status,
+    )
+    return [WorkflowOut.from_db_workflow(workflow, versions=workflow.versions) for workflow in workflows]
 
 
 @router.post("", status_code=status.HTTP_201_CREATED, summary="Create a new workflow")
diff --git a/app/crud/crud_workflow.py b/app/crud/crud_workflow.py
index 968e7dc..3a919d9 100644
--- a/app/crud/crud_workflow.py
+++ b/app/crud/crud_workflow.py
@@ -1,8 +1,9 @@
 from uuid import UUID
 
 from clowmdb.models import Workflow, WorkflowVersion
-from sqlalchemy import delete, select
+from sqlalchemy import delete, or_, select
 from sqlalchemy.ext.asyncio import AsyncSession
+from sqlalchemy.orm import joinedload
 
 from app.crud.crud_workflow_version import CRUDWorkflowVersion
 from app.schemas.workflow import WorkflowIn
@@ -11,10 +12,12 @@ from app.schemas.workflow import WorkflowIn
 class CRUDWorkflow:
     @staticmethod
     async def list(
-        db: AsyncSession, name_substring: str | None = None, unpublished_version: bool = False
+        db: AsyncSession,
+        name_substring: str | None = None,
+        version_status: list[WorkflowVersion.Status] | None = None,
     ) -> list[Workflow]:
         """
-        List all workflows.
+        List all workflows. Populates the version attribute of the workflows.
 
         Parameters
         ----------
@@ -22,20 +25,24 @@ class CRUDWorkflow:
             Async database session to perform query on.
         name_substring : str | None, default None
             Substring to filter for in the name of a workflow.
-        unpublished_version : bool, default False
-            Filter for workflows that have an unpublished version
+        version_status : list[clowmdb.models.WorkflowVersion.Status] | None, default None
+            Filter versions of a workflow based on the status. Removes workflows that have no version after this filter.
 
         Returns
         -------
         workflows : list[app.models.user.User]
             List of workflows.
         """
-        stmt = select(Workflow)
+        stmt = select(Workflow).options(joinedload(Workflow.versions))
         if name_substring is not None:
             stmt = stmt.where(Workflow.name.contains(name_substring))
-        if unpublished_version:
-            stmt = stmt.where(Workflow.versions.any(WorkflowVersion.status == WorkflowVersion.Status.CREATED))
-        return (await db.execute(stmt)).scalars().all()
+        if version_status is not None:
+            stmt = stmt.options(
+                joinedload(
+                    Workflow.versions.and_(or_(*[WorkflowVersion.status == status for status in version_status]))
+                )
+            )
+        return [w for w in (await db.execute(stmt)).scalars().unique().all() if len(w.versions) > 0]
 
     @staticmethod
     async def delete(db: AsyncSession, workflow_id: UUID | bytes) -> None:
diff --git a/app/crud/crud_workflow_version.py b/app/crud/crud_workflow_version.py
index 8b51189..9e1f5cd 100644
--- a/app/crud/crud_workflow_version.py
+++ b/app/crud/crud_workflow_version.py
@@ -18,6 +18,8 @@ class CRUDWorkflowVersion:
             Async database session to perform query on.
         git_commit_hash : str
             Git commit git_commit_hash of the version.
+        populate_workflow: boolean, default False
+            Flag if to populate the workflow attribute.
 
         Returns
         -------
@@ -78,7 +80,7 @@ class CRUDWorkflowVersion:
             Async database session to perform query on.
         wid : bytes | uuid.UUID
             Git commit git_commit_hash of the version.
-        version_status : list[clowmdb.models.WorkflowVersion.Status] | None, default
+        version_status : list[clowmdb.models.WorkflowVersion.Status] | None, default None
             Filter versions based on the status
 
         Returns
diff --git a/app/tests/api/test_workflow.py b/app/tests/api/test_workflow.py
index 4d7f776..b66c05d 100644
--- a/app/tests/api/test_workflow.py
+++ b/app/tests/api/test_workflow.py
@@ -257,7 +257,7 @@ class TestWorkflowRoutesList(_TestWorkflowRoutes):
         self, client: AsyncClient, random_user: UserWithAuthHeader, random_workflow: WorkflowOut
     ) -> None:
         """
-        Test for creating a workflow where the git commit is already in the system.
+        Test for listing all workflows in the system.
 
         Parameters
         ----------
@@ -268,7 +268,11 @@ class TestWorkflowRoutesList(_TestWorkflowRoutes):
         random_workflow : app.schemas.workflow.WorkflowOut
             Random workflow for testing. pytest fixture.
         """
-        response = await client.get(self.base_path, headers=random_user.auth_headers)
+        response = await client.get(
+            self.base_path,
+            headers=random_user.auth_headers,
+            params={"version_status": WorkflowVersion.Status.CREATED.name},
+        )
         assert response.status_code == status.HTTP_200_OK
         workflows = response.json()
         assert len(workflows) == 1
diff --git a/app/tests/crud/test_workflow.py b/app/tests/crud/test_workflow.py
index b95ed14..e878f66 100644
--- a/app/tests/crud/test_workflow.py
+++ b/app/tests/crud/test_workflow.py
@@ -40,7 +40,7 @@ class TestWorkflowCRUDGet:
         random_workflow : app.schemas.workflow.WorkflowOut
             Random bucket for testing. pytest fixture.
         """
-        workflows = await CRUDWorkflow.list(db, unpublished_version=True)
+        workflows = await CRUDWorkflow.list(db, version_status=[WorkflowVersion.Status.CREATED])
         assert len(workflows) == 1
         assert workflows[0].workflow_id == random_workflow.workflow_id
 
-- 
GitLab