Skip to content

Commit

Permalink
Add job runtime info to sample
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
lkeegan committed Nov 6, 2024
1 parent b8b797b commit 869616b
Show file tree
Hide file tree
Showing 13 changed files with 80 additions and 22 deletions.
12 changes: 8 additions & 4 deletions backend/src/predicTCR_server/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
)
)
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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,
)
Expand Down
3 changes: 2 additions & 1 deletion backend/tests/helpers/flask_test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const login_title = computed(() => {
</fwb-navbar-collapse>
</template>
</fwb-navbar>
<div class="flex flex-col items-center justify-center">
<div class="flex flex-col items-stretch justify-center">
<RouterView />
</div>
</template>
45 changes: 37 additions & 8 deletions frontend/src/components/SamplesTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}>();
Expand Down Expand Up @@ -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);
}
</script>

<template>
Expand All @@ -78,6 +102,7 @@ function delete_current_sample() {
<fwb-table-head-cell>Tumor type</fwb-table-head-cell>
<fwb-table-head-cell>Source</fwb-table-head-cell>
<fwb-table-head-cell>Status</fwb-table-head-cell>
<fwb-table-head-cell v-if="admin">Runtime</fwb-table-head-cell>
<fwb-table-head-cell>Inputs</fwb-table-head-cell>
<fwb-table-head-cell>Results</fwb-table-head-cell>
<fwb-table-head-cell v-if="admin">Actions</fwb-table-head-cell>
Expand All @@ -88,15 +113,16 @@ function delete_current_sample() {
:key="sample.id"
:class="sample.status === 'failed' ? '!bg-red-200' : '!bg-slate-50'"
>
<fwb-table-cell v-if="admin">{{ sample["id"] }}</fwb-table-cell>
<fwb-table-cell v-if="admin">{{ sample.id }}</fwb-table-cell>
<fwb-table-cell>{{
new Date(sample["timestamp"] * 1000).toLocaleDateString("de-DE")
timestamp_to_date(sample.timestamp)
}}</fwb-table-cell>
<fwb-table-cell v-if="admin">{{ sample["email"] }}</fwb-table-cell>
<fwb-table-cell>{{ sample["name"] }}</fwb-table-cell>
<fwb-table-cell>{{ sample["tumor_type"] }}</fwb-table-cell>
<fwb-table-cell>{{ sample["source"] }}</fwb-table-cell>
<fwb-table-cell>{{ sample["status"] }}</fwb-table-cell>
<fwb-table-cell v-if="admin">{{ sample.email }}</fwb-table-cell>
<fwb-table-cell>{{ sample.name }}</fwb-table-cell>
<fwb-table-cell>{{ sample.tumor_type }}</fwb-table-cell>
<fwb-table-cell>{{ sample.source }}</fwb-table-cell>
<fwb-table-cell>{{ sample.status }}</fwb-table-cell>
<fwb-table-cell v-if="admin">{{ job_runtime(sample) }}</fwb-table-cell>
<fwb-table-cell>
<fwb-a
href=""
Expand Down Expand Up @@ -151,6 +177,9 @@ function delete_current_sample() {
</fwb-table-row>
</fwb-table-body>
</fwb-table>
<fwb-button v-if="admin" class="mt-2" @click="download_samples_as_csv"
>Download as CSV</fwb-button
>

<fwb-modal size="lg" v-if="show_resubmit_modal" @close="close_modals">
<template #header>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/SettingsTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ function update_settings() {
:label="`Timeout for runner jobs: ${settings.runner_job_timeout_mins} minutes`"
class="mb-2"
/>
<fwb-button @click="update_settings" color="green">
<fwb-button @click="update_settings" class="mt-2" color="green">
Save settings</fwb-button
>
</div>
Expand Down
16 changes: 15 additions & 1 deletion frontend/src/components/UsersTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
FwbRange,
} from "flowbite-vue";
import type { User } from "@/utils/types";
import { apiClient, logout } from "@/utils/api-client";
import { apiClient, download_string_as_file, logout } from "@/utils/api-client";
import { ref, computed } from "vue";
const props = defineProps<{
Expand Down Expand Up @@ -63,6 +63,17 @@ function update_user() {
console.log(error);
});
}
function download_users_as_csv() {
let csv =
"Id,Email,Activated,Enabled,FullResults,Quota,SubmissionIntervalMinutes,Admin\n";
for (const user of users.value) {
if (!user.is_runner) {
csv += `${user.id},${user.email},${user.activated},${user.enabled},${user.full_results},${user.quota},${user.submission_interval_minutes},${user.is_admin}\n`;
}
}
download_string_as_file("users.csv", csv);
}
</script>

<template>
Expand Down Expand Up @@ -114,6 +125,9 @@ function update_user() {
</fwb-table-row>
</fwb-table-body>
</fwb-table>
<fwb-button class="mt-2" @click="download_users_as_csv"
>Download as CSV</fwb-button
>

<fwb-modal size="lg" v-if="show_modal" @close="close_modal">
<template #header>
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/utils/api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,12 @@ export function logout() {
user.user = null;
user.token = "";
}

export function download_string_as_file(filename: string, str: string) {
const link = document.createElement("a");
const file = new Blob([str], { type: "text/plain" });
link.href = URL.createObjectURL(file);
link.download = filename;
link.click();
URL.revokeObjectURL(link.href);
}
3 changes: 2 additions & 1 deletion frontend/src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export type Sample = {
tumor_type: string;
source: number;
timestamp: number;
timestamp_results: number;
timestamp_job_start: number;
timestamp_job_end: number;
status: string;
has_results_zip: boolean;
};
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/views/AboutView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function openModalSignup() {
</script>

<template>
<main>
<main class="flex flex-col items-center justify-center">
<fwb-jumbotron
header-text="predicTCR v2"
header-classes="text-slate-100"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/views/ActivateView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ apiClient
</script>

<template>
<main>
<main class="flex flex-col items-center justify-center">
<CardComponent :title="title">
<p>{{ message }}</p>
<p>Go to <RouterLink to="/login">login</RouterLink> page.</p>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/views/LoginView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const userStore = useUserStore();
</script>

<template>
<main>
<main class="flex flex-col items-center justify-center">
<template v-if="userStore.user !== null">
<AccountComponent></AccountComponent>
</template>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/views/ResetPasswordView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ function reset_password() {
</script>

<template>
<main>
<main class="flex flex-col items-center justify-center">
<CardComponent :title="title" :icon="icon">
<form @submit.prevent="reset_password" v-if="show_form">
<p>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/views/SamplesView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ function add_sample() {
</script>

<template>
<main>
<main class="flex flex-col items-center justify-center">
<ListComponent>
<ListItem title="Submit a sample">
<template v-if="submit_message.length > 0">
Expand Down

0 comments on commit 869616b

Please sign in to comment.