Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add summary endpoint #398

Merged
merged 9 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def analysis() -> Analysis:
type=TYPES[0],
workflow_manager=WorkflowManager.SLURM,
is_visible=True,
order_id=1,
)
session: Session = get_session()
session.add(analysis)
Expand Down
15 changes: 15 additions & 0 deletions tests/integration/endpoints/test_get_summaries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from flask.testing import FlaskClient
from http import HTTPStatus

from trailblazer.store.models import Analysis


def test_get_existing_analysis(client: FlaskClient, analysis: Analysis):
# GIVEN an order with an analysis
order_id: int = analysis.order_id

# WHEN requesting a summary for the analyses in the order
response = client.get(f"/api/v1/summary?order_ids[]={order_id}")

# THEN it gives a success response
assert response.status_code == HTTPStatus.OK
2 changes: 1 addition & 1 deletion trailblazer/cli/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from trailblazer.io.controller import ReadFile
from trailblazer.models import Config
from trailblazer.server.wiring import setup_dependency_injection
from trailblazer.services.analysis_service import AnalysisService
from trailblazer.services.analysis_service.analysis_service import AnalysisService
from trailblazer.services.job_service import JobService
from trailblazer.store.database import get_session, initialize_database
from trailblazer.store.models import Analysis, User
Expand Down
2 changes: 1 addition & 1 deletion trailblazer/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from dependency_injector import containers, providers

from trailblazer.clients.slurm_cli_client.slurm_cli_client import SlurmCLIClient
from trailblazer.services.analysis_service import AnalysisService
from trailblazer.services.analysis_service.analysis_service import AnalysisService
from trailblazer.services.job_service import JobService
from trailblazer.services.slurm.slurm_cli_service.slurm_cli_service import SlurmCLIService
from trailblazer.store.store import Store
Expand Down
5 changes: 5 additions & 0 deletions trailblazer/dto/summaries_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from pydantic import BaseModel


class SummariesRequest(BaseModel):
order_ids: list[int]
14 changes: 14 additions & 0 deletions trailblazer/dto/summaries_response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from pydantic import BaseModel


class Summary(BaseModel):
order_id: int
total: int
delivered: int
running: int
cancelled: int
failed: int


class SummariesResponse(BaseModel):
summaries: list[Summary]
16 changes: 15 additions & 1 deletion trailblazer/server/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
)
from trailblazer.dto.analyses_response import UpdateAnalysesResponse
from trailblazer.dto.create_analysis_request import CreateAnalysisRequest
from trailblazer.dto.summaries_request import SummariesRequest
from trailblazer.dto.summaries_response import SummariesResponse
from trailblazer.dto.update_analyses import UpdateAnalyses
from trailblazer.exc import MissingAnalysis
from trailblazer.server.ext import store
Expand All @@ -36,9 +38,10 @@
parse_analysis_update_request,
parse_get_failed_jobs_request,
parse_job_create_request,
parse_summaries_request,
stringify_timestamps,
)
from trailblazer.services.analysis_service import AnalysisService
from trailblazer.services.analysis_service.analysis_service import AnalysisService
from trailblazer.services.job_service import JobService
from trailblazer.store.models import Info

Expand Down Expand Up @@ -131,6 +134,17 @@ def update_analysis(
return jsonify(error=str(error)), HTTPStatus.BAD_REQUEST


@blueprint.route("/summary", methods=["GET"])
@inject
def get_summaries(analysis_service: AnalysisService = Provide[Container.analysis_service]):
try:
request_data: SummariesRequest = parse_summaries_request(request)
response: SummariesResponse = analysis_service.get_summaries(request_data)
return jsonify(response.model_dump()), HTTPStatus.OK
except ValidationError as error:
return jsonify(error=str(error)), HTTPStatus.BAD_REQUEST
seallard marked this conversation as resolved.
Show resolved Hide resolved


@blueprint.route("/info")
def info():
"""Display metadata about database."""
Expand Down
10 changes: 10 additions & 0 deletions trailblazer/server/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from trailblazer.dto import AnalysesRequest, AnalysisUpdateRequest, FailedJobsRequest
from trailblazer.dto.create_job_request import CreateJobRequest
from trailblazer.dto.summaries_request import SummariesRequest


def parse_analyses_request(request: Request) -> AnalysesRequest:
Expand Down Expand Up @@ -39,3 +40,12 @@ def stringify_timestamps(data: dict) -> dict[str, str]:

def parse_job_create_request(request: Request) -> CreateJobRequest:
return CreateJobRequest.model_validate(request.json)


def parse_summaries_request(request: Request) -> SummariesRequest:
query_params = {
key[:-2]: request.args.getlist(key)
for key in request.args.keys()
if key_has_list_of_values(key)
}
seallard marked this conversation as resolved.
Show resolved Hide resolved
return SummariesRequest.model_validate(query_params)
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@
CreateAnalysisRequest,
)
from trailblazer.dto.analyses_response import UpdateAnalysesResponse
from trailblazer.dto.summaries_request import SummariesRequest
from trailblazer.dto.summaries_response import SummariesResponse, Summary
from trailblazer.dto.update_analyses import UpdateAnalyses
from trailblazer.exc import MissingAnalysis
from trailblazer.services.utils import create_update_analyses_response, create_analysis_response
from trailblazer.services.analysis_service.utils import (
create_analysis_response,
create_summary,
create_update_analyses_response,
)
from trailblazer.store.models import Analysis, Job
from trailblazer.store.store import Store

Expand Down Expand Up @@ -58,3 +64,11 @@ def create_analyses_response(

def update_ongoing_analyses(self) -> None:
self.store.update_ongoing_analyses()

def get_summaries(self, request_data: SummariesRequest) -> SummariesResponse:
summaries: list[Summary] = []
for order_id in request_data.order_ids:
analyses: list[Analysis] = self.store.get_analyses_by_order_id(order_id)
summary: Summary = create_summary(analyses)
summaries.append(summary)
return SummariesResponse(summaries=summaries)
41 changes: 41 additions & 0 deletions trailblazer/services/analysis_service/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from trailblazer.constants import TrailblazerStatus
from trailblazer.dto.analyses_response import UpdateAnalysesResponse
from trailblazer.dto.analysis_response import AnalysisResponse
from trailblazer.dto.summaries_response import Summary
from trailblazer.store.models import Analysis


def get_status_count(analyses: list[Analysis], status: TrailblazerStatus) -> int:
return len([a for a in analyses if a.status == status])


def create_summary(analyses: list[Analysis]) -> Summary:
total: int = len(analyses)
delivered: int = get_status_count(analyses=analyses, status=TrailblazerStatus.COMPLETED)
running: int = get_status_count(analyses=analyses, status=TrailblazerStatus.RUNNING)
cancelled: int = get_status_count(analyses=analyses, status=TrailblazerStatus.CANCELLED)
failed = get_status_count(analyses=analyses, status=TrailblazerStatus.FAILED)
seallard marked this conversation as resolved.
Show resolved Hide resolved
return Summary(
order_id=analyses[0].order_id,
seallard marked this conversation as resolved.
Show resolved Hide resolved
total=total,
delivered=delivered,
running=running,
cancelled=cancelled,
failed=failed,
)


def create_analysis_response(analysis: Analysis) -> AnalysisResponse:
analysis_data: dict = analysis.to_dict()
analysis_data["jobs"] = [job.to_dict() for job in analysis.analysis_jobs]
analysis_data["upload_jobs"] = [job.to_dict() for job in analysis.upload_jobs]
analysis_data["user"] = analysis.user.to_dict() if analysis.user else None
return AnalysisResponse.model_validate(analysis_data)


def create_update_analyses_response(analyses: list[Analysis]) -> UpdateAnalysesResponse:
response_data: list[dict] = []
for analysis in analyses:
analysis_data = analysis.to_dict()
response_data.append(analysis_data)
return UpdateAnalysesResponse(analyses=response_data)
seallard marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions trailblazer/services/job_service/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from trailblazer.services.job_service.job_service import JobService
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import logging

from trailblazer.dto import CreateJobRequest, FailedJobsRequest, FailedJobsResponse, JobResponse
from trailblazer.services.job_service.utils import create_failed_jobs_response, create_job_response
from trailblazer.services.slurm.dtos import SlurmJobInfo
from trailblazer.services.slurm.slurm_service import SlurmService
from trailblazer.services.utils import create_job_response, create_failed_jobs_response
from trailblazer.store.models import Job
from trailblazer.store.store import Store
from trailblazer.utils.datetime import get_date_number_of_days_ago
Expand Down
13 changes: 13 additions & 0 deletions trailblazer/services/job_service/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from trailblazer.dto.failed_jobs_response import FailedJobsResponse
from trailblazer.dto.job_response import JobResponse
from trailblazer.store.models import Job


def create_job_response(job: Job) -> JobResponse:
return JobResponse(
slurm_id=job.slurm_id, analysis_id=job.analysis_id, status=job.status, id=job.id
)


def create_failed_jobs_response(failed_job_statistics: list[dict]) -> FailedJobsResponse:
return FailedJobsResponse(jobs=failed_job_statistics)
30 changes: 0 additions & 30 deletions trailblazer/services/utils.py

This file was deleted.

7 changes: 7 additions & 0 deletions trailblazer/store/crud/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,10 @@ def get_job_by_id(self, job_id: int) -> Job | None:
jobs=self.get_query(Job),
job_id=job_id,
).first()

def get_analyses_by_order_id(self, order_id: int) -> list[Analysis]:
return apply_analysis_filter(
analyses=self.get_query(Analysis),
filter_functions=[AnalysisFilter.FILTER_BY_ORDER_ID],
order_id=order_id,
).all()
Loading