Skip to content
Snippets Groups Projects
Verified Commit 68b6beda authored by Daniel Göbel's avatar Daniel Göbel
Browse files

Add endpoint to fetch statistics for workflow

#24
parent b6a40754
No related branches found
No related tags found
1 merge request!30Resolve "Workflow usage statistics"
Pipeline #28079 passed
This commit is part of merge request !30. Comments created here will be created in the context of that merge request.
from typing import Annotated, Any, Awaitable, Callable from typing import Annotated, Any, Awaitable, Callable
from clowmdb.models import Workflow, WorkflowVersion from clowmdb.models import Workflow, WorkflowVersion
from fastapi import APIRouter, BackgroundTasks, Depends, File, HTTPException, Query, UploadFile, status from fastapi import APIRouter, BackgroundTasks, Depends, File, HTTPException, Query, Response, UploadFile, status
from app.api.dependencies import AuthorizationDependency, CurrentUser, CurrentWorkflow, DBSession, HTTPClient, S3Service from app.api.dependencies import AuthorizationDependency, CurrentUser, CurrentWorkflow, DBSession, HTTPClient, S3Service
from app.api.utils import check_repo, upload_icon from app.api.utils import check_repo, upload_icon
from app.core.config import settings from app.core.config import settings
from app.crud import CRUDWorkflow, CRUDWorkflowVersion from app.crud import CRUDWorkflow, CRUDWorkflowVersion
from app.git_repository import build_repository from app.git_repository import build_repository
from app.schemas.workflow import WorkflowIn, WorkflowOut from app.schemas.workflow import WorkflowIn, WorkflowOut, WorkflowStatistic
from app.schemas.workflow_version import WorkflowVersionFull, WorkflowVersionUpdate from app.schemas.workflow_version import WorkflowVersionFull, WorkflowVersionUpdate
router = APIRouter(prefix="/workflows", tags=["Workflow"]) router = APIRouter(prefix="/workflows", tags=["Workflow"])
...@@ -72,7 +72,7 @@ async def list_workflows( ...@@ -72,7 +72,7 @@ async def list_workflows(
rbac_operation = "list_filter" rbac_operation = "list_filter"
await authorization(rbac_operation) await authorization(rbac_operation)
workflows: list[Workflow] = await CRUDWorkflow.list( workflows: list[Workflow] = await CRUDWorkflow.list_workflows(
db, db,
name_substring=name_substring, name_substring=name_substring,
developer_id=developer_id, developer_id=developer_id,
...@@ -205,6 +205,34 @@ async def get_workflow( ...@@ -205,6 +205,34 @@ async def get_workflow(
return WorkflowOut.from_db_workflow(workflow, versions) return WorkflowOut.from_db_workflow(workflow, versions)
@router.get("/{wid}/statistics", status_code=status.HTTP_200_OK, summary="Get statistics for a workflow")
async def get_workflow_statistics(
workflow: CurrentWorkflow, db: DBSession, authorization: Authorization, response: Response
) -> list[WorkflowStatistic]:
"""
Get the number of started workflow per day.
\f
Parameters
----------
workflow : clowmdb.models.Workflow
Workflow with given ID. Dependency Injection.
db : sqlalchemy.ext.asyncio.AsyncSession.
Async database session to perform query on. Dependency Injection.
authorization : Callable[[str], Awaitable[Any]]
Async function to ask the auth service for authorization. Dependency Injection.
response : fastapi.Response
Temporal Response object. Dependency Injection.
Returns
-------
statistics : list[app.schema.Workflow.WorkflowStatistic]
"""
await authorization("read")
# Instruct client to cache response for 1 hour
response.headers["Cache-Control"] = "max-age=3600"
return await CRUDWorkflow.statistics(db, workflow.workflow_id)
@router.delete("/{wid}", status_code=status.HTTP_204_NO_CONTENT, summary="Delete a workflow") @router.delete("/{wid}", status_code=status.HTTP_204_NO_CONTENT, summary="Delete a workflow")
async def delete_workflow( async def delete_workflow(
background_tasks: BackgroundTasks, background_tasks: BackgroundTasks,
......
from uuid import UUID from uuid import UUID
from clowmdb.models import Workflow, WorkflowVersion from clowmdb.models import Workflow, WorkflowExecution, WorkflowVersion
from sqlalchemy import delete, or_, select from sqlalchemy import Date, cast, delete, func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
from app.crud.crud_workflow_version import CRUDWorkflowVersion from app.crud.crud_workflow_version import CRUDWorkflowVersion
from app.schemas.workflow import WorkflowIn from app.schemas.workflow import WorkflowIn, WorkflowStatistic
class CRUDWorkflow: class CRUDWorkflow:
@staticmethod @staticmethod
async def list( async def list_workflows(
db: AsyncSession, db: AsyncSession,
name_substring: str | None = None, name_substring: str | None = None,
developer_id: str | None = None, developer_id: str | None = None,
...@@ -66,6 +66,34 @@ class CRUDWorkflow: ...@@ -66,6 +66,34 @@ class CRUDWorkflow:
await db.execute(stmt) await db.execute(stmt)
await db.commit() await db.commit()
@staticmethod
async def statistics(db: AsyncSession, workflow_id: bytes | UUID) -> list[WorkflowStatistic]:
"""
Calculate the number of workflows started per day for a specific workflow
Parameters
----------
db : sqlalchemy.ext.asyncio.AsyncSession.
Async database session to perform query on.
workflow_id : bytes | uuid.UUID
UID of a workflow.
Returns
-------
stat : list[app.schemas.Workflow.WorkflowStatistic]
List of datapoints
"""
wid = workflow_id.bytes if isinstance(workflow_id, UUID) else workflow_id
stmt = (
select(cast(WorkflowExecution.start_time, Date).label("day"), func.count())
.select_from(WorkflowExecution)
.join(WorkflowVersion)
.where(WorkflowVersion._workflow_id == wid)
.group_by("day")
.order_by("day")
)
return [WorkflowStatistic(day=row.day, count=row.count) for row in await db.execute(stmt)]
@staticmethod @staticmethod
async def get(db: AsyncSession, workflow_id: UUID | bytes) -> Workflow | None: async def get(db: AsyncSession, workflow_id: UUID | bytes) -> Workflow | None:
""" """
......
from datetime import date
from uuid import UUID from uuid import UUID
from clowmdb.models import Workflow as WorkflowDB from clowmdb.models import Workflow as WorkflowDB
...@@ -67,3 +68,8 @@ class WorkflowOut(_BaseWorkflow): ...@@ -67,3 +68,8 @@ class WorkflowOut(_BaseWorkflow):
versions=temp_versions, versions=temp_versions,
developer_id=db_workflow.developer_id, developer_id=db_workflow.developer_id,
) )
class WorkflowStatistic(BaseModel):
day: date = Field(..., description="Day of the datapoint", example=date(day=1, month=1, year=2023))
count: int = Field(..., description="Number of started workflows on that day", example=1)
...@@ -24,7 +24,7 @@ class TestWorkflowCRUDList: ...@@ -24,7 +24,7 @@ class TestWorkflowCRUDList:
random_workflow : app.schemas.workflow.WorkflowOut random_workflow : app.schemas.workflow.WorkflowOut
Random bucket for testing. pytest fixture. Random bucket for testing. pytest fixture.
""" """
workflows = await CRUDWorkflow.list(db) workflows = await CRUDWorkflow.list_workflows(db)
assert len(workflows) == 1 assert len(workflows) == 1
assert workflows[0].workflow_id == random_workflow.workflow_id assert workflows[0].workflow_id == random_workflow.workflow_id
...@@ -44,7 +44,7 @@ class TestWorkflowCRUDList: ...@@ -44,7 +44,7 @@ class TestWorkflowCRUDList:
random_user : app.tests.utils.user.UserWithAuthHeader random_user : app.tests.utils.user.UserWithAuthHeader
Random user for testing. pytest fixture. Random user for testing. pytest fixture.
""" """
workflows = await CRUDWorkflow.list(db, developer_id=random_user.user.uid) workflows = await CRUDWorkflow.list_workflows(db, developer_id=random_user.user.uid)
assert len(workflows) == 1 assert len(workflows) == 1
assert workflows[0].workflow_id == random_workflow.workflow_id assert workflows[0].workflow_id == random_workflow.workflow_id
...@@ -60,7 +60,7 @@ class TestWorkflowCRUDList: ...@@ -60,7 +60,7 @@ class TestWorkflowCRUDList:
random_workflow : app.schemas.workflow.WorkflowOut random_workflow : app.schemas.workflow.WorkflowOut
Random bucket for testing. pytest fixture. Random bucket for testing. pytest fixture.
""" """
workflows = await CRUDWorkflow.list(db, version_status=[WorkflowVersion.Status.CREATED]) workflows = await CRUDWorkflow.list_workflows(db, version_status=[WorkflowVersion.Status.CREATED])
assert len(workflows) == 1 assert len(workflows) == 1
assert workflows[0].workflow_id == random_workflow.workflow_id assert workflows[0].workflow_id == random_workflow.workflow_id
...@@ -81,7 +81,7 @@ class TestWorkflowCRUDList: ...@@ -81,7 +81,7 @@ class TestWorkflowCRUDList:
substring_indices = sorted(random.choices(range(len(random_workflow.name)), k=2)) substring_indices = sorted(random.choices(range(len(random_workflow.name)), k=2))
random_substring = random_workflow.name[substring_indices[0] : substring_indices[1]] random_substring = random_workflow.name[substring_indices[0] : substring_indices[1]]
workflows = await CRUDWorkflow.list(db, name_substring=random_substring) workflows = await CRUDWorkflow.list_workflows(db, name_substring=random_substring)
assert len(workflows) > 0 assert len(workflows) > 0
assert random_workflow.workflow_id in map(lambda w: w.workflow_id, workflows) assert random_workflow.workflow_id in map(lambda w: w.workflow_id, workflows)
...@@ -97,7 +97,7 @@ class TestWorkflowCRUDList: ...@@ -97,7 +97,7 @@ class TestWorkflowCRUDList:
random_workflow : app.schemas.workflow.WorkflowOut random_workflow : app.schemas.workflow.WorkflowOut
Random bucket for testing. pytest fixture. Random bucket for testing. pytest fixture.
""" """
workflows = await CRUDWorkflow.list(db, name_substring=2 * random_workflow.name) workflows = await CRUDWorkflow.list_workflows(db, name_substring=2 * random_workflow.name)
assert sum(1 for w in workflows if w.workflow_id == random_workflow.workflow_id) == 0 assert sum(1 for w in workflows if w.workflow_id == random_workflow.workflow_id) == 0
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment