From 651740bc72e14e097260e4d1b5cd6679106f905e Mon Sep 17 00:00:00 2001 From: Liam Keegan Date: Wed, 2 Oct 2024 17:30:00 +0200 Subject: [PATCH] Add endpoint to check if user can submit job - add /api/user_submit_message endpoint - add get_user_if_allowed_to_submit() to model, refactor add_new_sample to use it - frontend displays message if user cannot currently submit (updates every 30 seconds) - resolves #14 --- backend/src/predicTCR_server/app.py | 7 + backend/src/predicTCR_server/model.py | 39 ++-- frontend/src/components/AccountComponent.vue | 18 +- frontend/src/views/SamplesView.vue | 191 +++++++++++-------- 4 files changed, 162 insertions(+), 93 deletions(-) diff --git a/backend/src/predicTCR_server/app.py b/backend/src/predicTCR_server/app.py index 34aa694..643a96b 100644 --- a/backend/src/predicTCR_server/app.py +++ b/backend/src/predicTCR_server/app.py @@ -27,6 +27,7 @@ send_password_reset_email, request_job, process_result, + get_user_if_allowed_to_submit, ) @@ -207,6 +208,12 @@ def result(): logger.info(f"Returning file {requested_file}") return flask.send_file(requested_file, as_attachment=True) + @app.route("/api/user_submit_message", methods=["GET"]) + @jwt_required() + def user_submit_message(): + user, message = get_user_if_allowed_to_submit(current_user.email) + return jsonify(message=message) + @app.route("/api/sample", methods=["POST"]) @jwt_required() def add_sample(): diff --git a/backend/src/predicTCR_server/model.py b/backend/src/predicTCR_server/model.py index 17f34d3..3991792 100644 --- a/backend/src/predicTCR_server/model.py +++ b/backend/src/predicTCR_server/model.py @@ -362,32 +362,43 @@ def reset_user_password(token: str, email: str, new_password: str) -> tuple[str, return "Password changed", 200 -def add_new_sample( - email: str, - name: str, - tumor_type: str, - source: str, - h5_file: FileStorage, - csv_file: FileStorage, -) -> tuple[Sample | None, str]: +def get_user_if_allowed_to_submit(email: str) -> tuple[User | None, str]: + logger.info(f"Checking if {email} can submit a job") user = db.session.execute( db.select(User).filter(User.email == email) ).scalar_one_or_none() if user is None: - return None, f"Unknown email address {email}" + return None, f"Unknown email address {email}." + if user.quota <= 0: + return None, "You have reached your sample submission quota." mins_since_last_submission = ( timestamp_now() - user.last_submission_timestamp ) // 60 logger.debug( f"{mins_since_last_submission}mins since last submission at {user.last_submission_timestamp}" ) - wait_time_mins = predicTCR_submission_interval_minutes - mins_since_last_submission - logger.debug(f"Submission interval: {predicTCR_submission_interval_minutes}mins") + wait_time_mins = user.submission_interval_minutes - mins_since_last_submission + logger.debug(f"Submission interval: {user.submission_interval_minutes}mins") logger.debug(f" -> wait time: {wait_time_mins}min") if wait_time_mins > 0: - return None, f"Your next submission is available in {wait_time_mins} minutes" - if user.quota <= 0: - return None, "You have reached your submission quota" + return ( + None, + f"Your next sample submission is available in {wait_time_mins} minute{'s' if wait_time_mins > 1 else ''}.", + ) + return user, "" + + +def add_new_sample( + email: str, + name: str, + tumor_type: str, + source: str, + h5_file: FileStorage, + csv_file: FileStorage, +) -> tuple[Sample | None, str]: + user, msg = get_user_if_allowed_to_submit(email) + if user is None: + return None, msg user.last_submission_timestamp = timestamp_now() user.quota -= 1 new_sample = Sample( diff --git a/frontend/src/components/AccountComponent.vue b/frontend/src/components/AccountComponent.vue index 96b0046..d4cd7c0 100644 --- a/frontend/src/components/AccountComponent.vue +++ b/frontend/src/components/AccountComponent.vue @@ -55,6 +55,7 @@ function do_change_password() {

You are currently logged in as {{ current_email }}

- - + {{ new_password_message }} - + {{ new_password2_message }} -import { ref } from "vue"; +import { ref, onUnmounted } from "vue"; import SamplesTable from "@/components/SamplesTable.vue"; import { apiClient, logout } from "@/utils/api-client"; import type { Sample } from "@/utils/types"; @@ -15,7 +15,6 @@ import { FwbTimelinePoint, FwbTimelineTitle, FwbAlert, - FwbModal, } from "flowbite-vue"; const tumor_types = [ @@ -99,18 +98,49 @@ async function on_csv_file_changed(event: Event) { const samples = ref([] as Sample[]); -apiClient - .get("samples") - .then((response) => { - samples.value = response.data; - console.log(samples.value); - }) - .catch((error) => { - if (error.response.status > 400) { - logout(); - } - console.log(error); - }); +function update_samples() { + apiClient + .get("samples") + .then((response) => { + samples.value = response.data; + console.log(samples.value); + }) + .catch((error) => { + if (error.response.status > 400) { + logout(); + } + console.log(error); + }); +} + +update_samples(); + +const submit_message = ref(""); + +let update_submit_message_timer = setInterval(() => { + update_submit_message(); +}, 30000); + +onUnmounted(() => { + clearInterval(update_submit_message_timer); +}); + +function update_submit_message() { + console.log("update_submit_message"); + apiClient + .get("user_submit_message") + .then((response) => { + submit_message.value = response.data.message; + }) + .catch((error) => { + if (error.response.status > 400) { + logout(); + } + console.log(error); + }); +} + +update_submit_message(); function add_sample() { let formData = new FormData(); @@ -126,7 +156,8 @@ function add_sample() { }, }) .then((response) => { - samples.value.push(response.data.sample); + update_samples(); + update_submit_message(); new_sample_error_message.value = ""; }) .catch((error) => { @@ -153,65 +184,70 @@ function add_sample() { Submit a sample -

- - - - - - Submit - - - {{ new_sample_error_message }} - -
+ + @@ -223,8 +259,11 @@ function add_sample() { My samples