diff --git a/README_DEPLOYMENT.md b/README_DEPLOYMENT.md index a2ba61e..14c8ad3 100644 --- a/README_DEPLOYMENT.md +++ b/README_DEPLOYMENT.md @@ -9,10 +9,11 @@ To deploy the latest version on a virtual machine with docker compose installed, download [docker-compose.yml](https://raw.githubusercontent.com/ssciwr/predicTCR/main/docker-compose.yml), then do ``` -sudo docker compose pull -sudo docker compose up -d +sudo docker compose pull && sudo docker compose up -d && sudo docker system prune -af ``` +The same command can be used to update the running website to use the latest available docker images. + The location of data directory, SSL keys and secret key should be set either in env vars or in a file `.env` in the same location as the docker compose.yml. @@ -60,9 +61,11 @@ sudo sqlite3 docker_volume/predicTCR.db sqlite> UPDATE user SET is_admin=true WHERE email='user@embl.de'; sqlite> .quit ``` + ### Visitor count To get a count of the unique visitor IPs from the nginx logs: + ``` sudo docker compose logs frontend --no-log-prefix | grep "GET" | awk '{print $1}' | sort | uniq | wc -l ``` diff --git a/backend/src/predicTCR_server/app.py b/backend/src/predicTCR_server/app.py index d28d766..38c461c 100644 --- a/backend/src/predicTCR_server/app.py +++ b/backend/src/predicTCR_server/app.py @@ -234,11 +234,12 @@ def add_sample(): name = form_as_dict.get("name", "") tumor_type = form_as_dict.get("tumor_type", "") source = form_as_dict.get("source", "") + platform = form_as_dict.get("platform", "") h5_file = request.files.get("h5_file") csv_file = request.files.get("csv_file") logger.info(f"Adding sample {name} from {email}") new_sample, error_message = add_new_sample( - email, name, tumor_type, source, h5_file, csv_file + email, name, tumor_type, source, platform, h5_file, csv_file ) if new_sample is not None: logger.info(" - > success") @@ -443,6 +444,7 @@ def runner_result(): global_quota=1000, tumor_types="Lung;Breast;Other", sources="TIL;PMBC;Other", + platforms="Illumina;Other", csv_required_columns="barcode;cdr3;chain", runner_job_timeout_mins=60, max_filesize_h5_mb=50, diff --git a/backend/src/predicTCR_server/model.py b/backend/src/predicTCR_server/model.py index 708161a..d13f4ed 100644 --- a/backend/src/predicTCR_server/model.py +++ b/backend/src/predicTCR_server/model.py @@ -51,6 +51,7 @@ class Settings(db.Model): global_quota: Mapped[int] = mapped_column(Integer, nullable=False) tumor_types: Mapped[str] = mapped_column(String, nullable=False) sources: Mapped[str] = mapped_column(String, nullable=False) + platforms: Mapped[str] = mapped_column(String, nullable=False) csv_required_columns: Mapped[str] = mapped_column(String, nullable=False) runner_job_timeout_mins: Mapped[int] = mapped_column(Integer, nullable=False) max_filesize_h5_mb: Mapped[int] = mapped_column(Integer, nullable=False) @@ -83,6 +84,7 @@ class Sample(db.Model): name: Mapped[str] = mapped_column(String(128), nullable=False) tumor_type: Mapped[str] = mapped_column(String(128), nullable=False) source: Mapped[str] = mapped_column(String(128), nullable=False) + platform: Mapped[str] = mapped_column(String(128), nullable=False) timestamp: 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) @@ -488,6 +490,7 @@ def add_new_sample( name: str, tumor_type: str, source: str, + platform: str, h5_file: FileStorage, csv_file: FileStorage, ) -> tuple[Sample | None, str]: @@ -504,6 +507,7 @@ def add_new_sample( name=name, tumor_type=tumor_type, source=source, + platform=platform, timestamp=timestamp_now(), timestamp_job_start=0, timestamp_job_end=0, diff --git a/backend/tests/helpers/flask_test_utils.py b/backend/tests/helpers/flask_test_utils.py index 0518f93..62e5a89 100644 --- a/backend/tests/helpers/flask_test_utils.py +++ b/backend/tests/helpers/flask_test_utils.py @@ -53,6 +53,7 @@ def add_test_samples(app, data_path: pathlib.Path): name=name, tumor_type=f"tumor_type{sample_id}", source=f"source{sample_id}", + platform=f"platform{sample_id}", timestamp=sample_id, timestamp_job_start=0, timestamp_job_end=0, diff --git a/backend/tests/test_app.py b/backend/tests/test_app.py index 97cebd5..79af176 100644 --- a/backend/tests/test_app.py +++ b/backend/tests/test_app.py @@ -141,6 +141,7 @@ def test_get_settings_valid(client): "id": 1, "sources": "TIL;PMBC;Other", "tumor_types": "Lung;Breast;Other", + "platforms": "Illumina;Other", "runner_job_timeout_mins": 60, "max_filesize_h5_mb": 50, "max_filesize_csv_mb": 10, @@ -158,6 +159,7 @@ def test_update_settings_valid(client): "id": 1, "sources": "a;b;g", "tumor_types": "1;2;6", + "platforms": "Alpha;Beta;Other", "runner_job_timeout_mins": 12, "max_filesize_h5_mb": 77, "max_filesize_csv_mb": 12, diff --git a/frontend/src/components/SamplesTable.vue b/frontend/src/components/SamplesTable.vue index e4cea3c..8fee878 100644 --- a/frontend/src/components/SamplesTable.vue +++ b/frontend/src/components/SamplesTable.vue @@ -84,9 +84,10 @@ function job_runtime(sample: Sample): string { } function download_samples_as_csv() { - let csv = "Id,Date,Email,SampleName,TumorType,Source,Status,Runtime\n"; + let csv = + "Id,Date,Email,SampleName,TumorType,Source,Platform,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`; + csv += `${sample.id},${timestamp_to_date(sample.timestamp)},${sample.email},${sample.name},${sample.tumor_type},${sample.source},${sample.platform},${sample.status},${job_runtime(sample)}\n`; } download_string_as_file("samples.csv", csv); } @@ -101,6 +102,7 @@ function download_samples_as_csv() { Sample Name Tumor type Source + Platform Status Runtime Inputs @@ -122,6 +124,7 @@ function download_samples_as_csv() { {{ sample.name }} {{ sample.tumor_type }} {{ sample.source }} + {{ sample.platform }} {{ sample.status }} {{ job_runtime(sample) }} diff --git a/frontend/src/components/SettingsTable.vue b/frontend/src/components/SettingsTable.vue index e3cfdfa..fd1f7cc 100644 --- a/frontend/src/components/SettingsTable.vue +++ b/frontend/src/components/SettingsTable.vue @@ -57,6 +57,11 @@ function update_settings() { class="mb-2" label="Sources (separated by ;)" > + { global_quota: 1, tumor_types: "", sources: "", + platforms: "", csv_required_columns: "", runner_job_timeout_mins: 1, about_md: "", diff --git a/frontend/src/utils/types.ts b/frontend/src/utils/types.ts index 32d9d7a..9b498d0 100644 --- a/frontend/src/utils/types.ts +++ b/frontend/src/utils/types.ts @@ -3,7 +3,8 @@ export type Sample = { email: string; name: string; tumor_type: string; - source: number; + source: string; + platform: string; timestamp: number; timestamp_job_start: number; timestamp_job_end: number; @@ -32,6 +33,7 @@ export type Settings = { global_quota: number; tumor_types: string; sources: string; + platforms: string; csv_required_columns: string; runner_job_timeout_mins: number; about_md: string; diff --git a/frontend/src/views/ActivateView.vue b/frontend/src/views/ActivateView.vue index 33e0e25..fa6fb7a 100644 --- a/frontend/src/views/ActivateView.vue +++ b/frontend/src/views/ActivateView.vue @@ -11,7 +11,6 @@ const icon = ref("bi-person-exclamation"); apiClient .get(`activate/${props.activation_token}`) .then((response) => { - console.log(response); message.value = response.data.message; icon.value = "bi-person-check"; title.value = diff --git a/frontend/src/views/ResetPasswordView.vue b/frontend/src/views/ResetPasswordView.vue index f03ec7a..1be6d46 100644 --- a/frontend/src/views/ResetPasswordView.vue +++ b/frontend/src/views/ResetPasswordView.vue @@ -39,7 +39,6 @@ function reset_password() { new_password: new_password.value, }) .then((response) => { - console.log(response); message.value = response.data.message; icon.value = "bi-person-check"; title.value = "Password reset successful"; diff --git a/frontend/src/views/SamplesView.vue b/frontend/src/views/SamplesView.vue index c33aa17..b6e2e62 100644 --- a/frontend/src/views/SamplesView.vue +++ b/frontend/src/views/SamplesView.vue @@ -4,7 +4,7 @@ import SamplesTable from "@/components/SamplesTable.vue"; import ListComponent from "@/components/ListComponent.vue"; import ListItem from "@/components/ListItem.vue"; import { apiClient, logout } from "@/utils/api-client"; -import type { Sample, Settings } from "@/utils/types"; +import type { Sample } from "@/utils/types"; import { FwbButton, FwbSelect, @@ -24,6 +24,7 @@ type OptionsType = { const tumor_types = ref([] as Array); const sources = ref([] as Array); +const platforms = ref([] as Array); const required_columns = ref([] as Array); function closeModalSubmit() { @@ -39,6 +40,7 @@ const agree_to_conditions = ref(false); const sample_name = ref(""); const tumor_type = ref(""); const source = ref(""); +const platform = ref(""); const selected_h5_file = ref(null as null | File); const h5_file_input_key = ref(0); const selected_csv_file = ref(null as null | File); @@ -68,10 +70,8 @@ async function validate_csv_file(file: File) { const lines = text.split(/\n/); if (lines.length >= 1) { const columns = lines[0].split(/,/); - console.log(columns); for (const required_column of required_columns.value) { if (!columns.includes(required_column)) { - console.log(`Missing header: ${required_column}`); return false; } } @@ -110,6 +110,7 @@ function string_to_options(str: string): Array { tumor_types.value = string_to_options(settingsStore.settings.tumor_types); sources.value = string_to_options(settingsStore.settings.sources); +platforms.value = string_to_options(settingsStore.settings.platforms); required_columns.value = settingsStore.settings.csv_required_columns.split(";"); const samples = ref([] as Sample[]); @@ -119,7 +120,6 @@ function update_samples() { .get("samples") .then((response) => { samples.value = response.data; - console.log(samples.value); }) .catch((error) => { if (error.response.status > 400) { @@ -144,7 +144,6 @@ onUnmounted(() => { }); function update_submit_message() { - console.log("update_submit_message"); apiClient .get("user_submit_message") .then((response) => { @@ -165,6 +164,7 @@ function add_sample() { formData.append("name", sample_name.value); formData.append("tumor_type", tumor_type.value); formData.append("source", source.value); + formData.append("platform", platform.value); formData.append("h5_file", selected_h5_file.value as File); formData.append("csv_file", selected_csv_file.value as File); apiClient @@ -225,6 +225,13 @@ function add_sample() { label="Source" class="mb-2" /> +