From 869616bb8d70657769059eea59b0c3667642a40a Mon Sep 17 00:00:00 2001 From: Liam Keegan Date: Tue, 5 Nov 2024 10:35:24 +0100 Subject: [PATCH] Add job runtime info to sample - Sample.timestamp_results -> Sample.timestamp_job_start - add Sample.timestamp_job_end - add runtime in format hh::mm:ss to admin Samples table - add button to download samples data as csv to admin view - also a download as csv button for the admin users table - resolves #47 --- backend/src/predicTCR_server/model.py | 12 ++++-- backend/tests/helpers/flask_test_utils.py | 3 +- frontend/src/App.vue | 2 +- frontend/src/components/SamplesTable.vue | 45 +++++++++++++++++++---- frontend/src/components/SettingsTable.vue | 2 +- frontend/src/components/UsersTable.vue | 16 +++++++- frontend/src/utils/api-client.ts | 9 +++++ frontend/src/utils/types.ts | 3 +- frontend/src/views/AboutView.vue | 2 +- frontend/src/views/ActivateView.vue | 2 +- frontend/src/views/LoginView.vue | 2 +- frontend/src/views/ResetPasswordView.vue | 2 +- frontend/src/views/SamplesView.vue | 2 +- 13 files changed, 80 insertions(+), 22 deletions(-) diff --git a/backend/src/predicTCR_server/model.py b/backend/src/predicTCR_server/model.py index 8f7e06c..046a298 100644 --- a/backend/src/predicTCR_server/model.py +++ b/backend/src/predicTCR_server/model.py @@ -81,7 +81,8 @@ class Sample(db.Model): tumor_type: Mapped[str] = mapped_column(String(128), nullable=False) source: Mapped[str] = mapped_column(String(128), nullable=False) timestamp: Mapped[int] = mapped_column(Integer, nullable=False) - timestamp_results: Mapped[int] = mapped_column(Integer, nullable=False) + timestamp_job_start: Mapped[int] = mapped_column(Integer, nullable=False) + timestamp_job_end: Mapped[int] = mapped_column(Integer, nullable=False) status: Mapped[Status] = mapped_column(Enum(Status), nullable=False) has_results_zip: Mapped[bool] = mapped_column(Boolean, nullable=False) @@ -161,7 +162,7 @@ def request_job() -> int | None: db.select(Sample).filter( (Sample.status == Status.RUNNING) & ( - timestamp_now() - Sample.timestamp_results + timestamp_now() - Sample.timestamp_job_start > job_timeout_minutes * 60 ) ) @@ -186,7 +187,8 @@ def request_job() -> int | None: return None else: logger.info(f" --> sample id {sample.id}") - sample.timestamp_results = timestamp_now() + sample.timestamp_job_start = timestamp_now() + sample.timestamp_job_end = 0 sample.status = Status.RUNNING db.session.commit() return sample.id @@ -210,6 +212,7 @@ def process_result( if job is None: logger.warning(f" --> Unknown job id {job_id}") return f"Unknown job id {job_id}", 400 + sample.timestamp_job_end = timestamp_now() job.timestamp_end = timestamp_now() if success: job.status = Status.COMPLETED @@ -496,7 +499,8 @@ def add_new_sample( tumor_type=tumor_type, source=source, timestamp=timestamp_now(), - timestamp_results=0, + timestamp_job_start=0, + timestamp_job_end=0, status=Status.QUEUED, has_results_zip=False, ) diff --git a/backend/tests/helpers/flask_test_utils.py b/backend/tests/helpers/flask_test_utils.py index e82bd21..3a2da58 100644 --- a/backend/tests/helpers/flask_test_utils.py +++ b/backend/tests/helpers/flask_test_utils.py @@ -54,7 +54,8 @@ def add_test_samples(app, data_path: pathlib.Path): tumor_type=f"tumor_type{sample_id}", source=f"source{sample_id}", timestamp=sample_id, - timestamp_results=0, + timestamp_job_start=0, + timestamp_job_end=0, status=status, has_results_zip=False, ) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index cab301a..df8300f 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -44,7 +44,7 @@ const login_title = computed(() => { -
+
diff --git a/frontend/src/components/SamplesTable.vue b/frontend/src/components/SamplesTable.vue index 78e9ab6..a438e59 100644 --- a/frontend/src/components/SamplesTable.vue +++ b/frontend/src/components/SamplesTable.vue @@ -18,11 +18,12 @@ import { download_result, download_admin_result, logout, + download_string_as_file, } from "@/utils/api-client"; import type { Sample } from "@/utils/types"; import { ref } from "vue"; -defineProps<{ +const props = defineProps<{ samples: Sample[]; admin: boolean; }>(); @@ -66,6 +67,29 @@ function delete_current_sample() { console.log(error); }); } + +function timestamp_to_date(timestamp_secs: number): string { + const secs_to_ms = 1000; + return new Date(timestamp_secs * secs_to_ms).toLocaleDateString("de-DE"); +} + +function job_runtime(sample: Sample): string { + // returns job runtime as hh::mm::ss + const runtime_secs = sample.timestamp_job_end - sample.timestamp_job_start; + const secs_to_ms = 1000; + if (runtime_secs <= 0) { + return "-"; + } + return new Date(runtime_secs * secs_to_ms).toISOString().slice(11, 19); +} + +function download_samples_as_csv() { + let csv = "Id,Date,Email,SampleName,TumorType,Source,Status,Runtime\n"; + for (const sample of props.samples) { + csv += `${sample.id},${timestamp_to_date(sample.timestamp)},${sample.email},${sample.name},${sample.tumor_type},${sample.source},${sample.status},${job_runtime(sample)}\n`; + } + download_string_as_file("samples.csv", csv); +}