Skip to content

Commit

Permalink
Add endpoint to check if user can submit job
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
lkeegan committed Oct 2, 2024
1 parent bd58bf2 commit 651740b
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 93 deletions.
7 changes: 7 additions & 0 deletions backend/src/predicTCR_server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
send_password_reset_email,
request_job,
process_result,
get_user_if_allowed_to_submit,
)


Expand Down Expand Up @@ -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():
Expand Down
39 changes: 25 additions & 14 deletions backend/src/predicTCR_server/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
18 changes: 15 additions & 3 deletions frontend/src/components/AccountComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ function do_change_password() {
<p>You are currently logged in as {{ current_email }}</p>
<p>
<fwb-button
class="my-2"
@click="
userStore.user = null;
userStore.token = '';
Expand All @@ -74,8 +75,8 @@ function do_change_password() {
placeholder="current password"
maxlength="256"
autocomplete="current-password"
class="mb-2"
/>
<label for="account_passwd_new">New Password:</label>
<fwb-input
v-model="new_password"
label="New password"
Expand All @@ -85,8 +86,13 @@ function do_change_password() {
placeholder="new password"
:title="new_password_message"
maxlength="256"
class="mb-2"
/>
<fwb-alert type="danger" v-if="new_password_message.length > 0">
<fwb-alert
type="danger"
v-if="new_password_message.length > 0"
class="mb-2"
>
{{ new_password_message }}
</fwb-alert>
<fwb-input
Expand All @@ -98,13 +104,19 @@ function do_change_password() {
placeholder="new password"
:title="new_password2_message"
maxlength="256"
class="mb-2"
/>
<fwb-alert type="danger" v-if="new_password2_message.length > 0">
<fwb-alert
type="danger"
v-if="new_password2_message.length > 0"
class="mb-2"
>
{{ new_password2_message }}
</fwb-alert>
<fwb-button
@click="do_change_password"
:title="new_password_message + ' ' + new_password2_message"
class="mb-2"
:disabled="
current_password.length === 0 ||
new_password.length === 0 ||
Expand Down
191 changes: 115 additions & 76 deletions frontend/src/views/SamplesView.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
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";
Expand All @@ -15,7 +15,6 @@ import {
FwbTimelinePoint,
FwbTimelineTitle,
FwbAlert,
FwbModal,
} from "flowbite-vue";
const tumor_types = [
Expand Down Expand Up @@ -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();
Expand All @@ -126,7 +156,8 @@ function add_sample() {
},
})
.then((response) => {

Check warning on line 158 in frontend/src/views/SamplesView.vue

View workflow job for this annotation

GitHub Actions / Frontend :: node 22

'response' is defined but never used
samples.value.push(response.data.sample);
update_samples();
update_submit_message();
new_sample_error_message.value = "";
})
.catch((error) => {
Expand All @@ -153,65 +184,70 @@ function add_sample() {
<fwb-timeline-content>
<fwb-timeline-title>Submit a sample</fwb-timeline-title>
<fwb-timeline-body>
<div class="flex flex-col mt-2">
<fwb-input
v-model="sample_name"
required
label="Sample name"
id="sample_name"
placeholder="pXYZ_ABC_c1"
maxlength="128"
class="mb-2"
/>
<fwb-select
v-model="tumor_type"
:options="tumor_types"
id="tumor_type"
label="Tumor type"
class="mb-2"
/>
<fwb-select
v-model="source"
:options="sources"
id="source"
label="Source"
class="mb-2"
/>
<fwb-file-input
type="file"
id="input_h5_file"
label="H5 input file"
name="h5 file"
@change="on_h5_file_changed($event)"
:key="h5_file_input_key"
accept=".h5,.he5,.hdf5"
title="Select the h5 file to upload"
class="mb-2"
/>
<fwb-file-input
type="file"
id="input_csv_file"
label="CSV input file"
name="csv file"
@change="on_csv_file_changed($event)"
:key="csv_file_input_key"
accept=".csv,.txt"
title="Select the csv file to upload"
class="mb-2"
/>
<fwb-button
@click="add_sample"
:disabled="
selected_h5_file === null ||
selected_csv_file === null ||
sample_name.length === 0
"
>Submit
</fwb-button>
<fwb-alert type="danger" v-if="new_sample_error_message">
{{ new_sample_error_message }}
</fwb-alert>
</div>
<template v-if="submit_message.length > 0">
{{ submit_message }}
</template>
<template v-else>
<div class="flex flex-col mt-2">
<fwb-input
v-model="sample_name"
required
label="Sample name"
id="sample_name"
placeholder="pXYZ_ABC_c1"
maxlength="128"
class="mb-2"
/>
<fwb-select
v-model="tumor_type"
:options="tumor_types"
id="tumor_type"
label="Tumor type"
class="mb-2"
/>
<fwb-select
v-model="source"
:options="sources"
id="source"
label="Source"
class="mb-2"
/>
<fwb-file-input
type="file"
id="input_h5_file"
label="H5 input file"
name="h5 file"
@change="on_h5_file_changed($event)"
:key="h5_file_input_key"
accept=".h5,.he5,.hdf5"
title="Select the h5 file to upload"
class="mb-2"
/>
<fwb-file-input
type="file"
id="input_csv_file"
label="CSV input file"
name="csv file"
@change="on_csv_file_changed($event)"
:key="csv_file_input_key"
accept=".csv,.txt"
title="Select the csv file to upload"
class="mb-2"
/>
<fwb-button
@click="add_sample"
:disabled="
selected_h5_file === null ||
selected_csv_file === null ||
sample_name.length === 0
"
>Submit
</fwb-button>
<fwb-alert type="danger" v-if="new_sample_error_message">
{{ new_sample_error_message }}
</fwb-alert>
</div>
</template>
</fwb-timeline-body>
</fwb-timeline-content>
</fwb-timeline-item>
Expand All @@ -223,8 +259,11 @@ function add_sample() {
<fwb-timeline-title>My samples</fwb-timeline-title>
<fwb-timeline-body>
<template v-if="samples.length > 0">
<p>Your samples:</p>
<SamplesTable :samples="samples" :admin="false"></SamplesTable>
<SamplesTable
:samples="samples"
:admin="false"
class="mt-2"
></SamplesTable>
</template>
<template v-else>
<p>You don't yet have any samples.</p>
Expand Down

0 comments on commit 651740b

Please sign in to comment.