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"
/>
+