Skip to content

Commit 212e244

Browse files
pbugniivan-cachen2401Amy Chen
authored
TN-3324 robust handling of unsortable patient list attributes (#4414)
Co-authored-by: Ivan Cvitkovic <[email protected]> Co-authored-by: Amy Chen <[email protected]> Co-authored-by: Amy Chen <[email protected]>
1 parent ffa7df7 commit 212e244

File tree

11 files changed

+70
-264
lines changed

11 files changed

+70
-264
lines changed

portal/models/patient_list.py

+46-42
Original file line numberDiff line numberDiff line change
@@ -53,49 +53,53 @@ def patient_list_update_patient(patient_id, research_study_id=None):
5353
if not user.has_role(ROLE.PATIENT.value):
5454
return
5555

56-
patient = PatientList.query.get(patient_id)
57-
new_record = False
58-
if not patient:
59-
new_record = True
60-
patient = PatientList(userid=patient_id)
61-
db.session.add(patient)
56+
from ..timeout_lock import TimeoutLock
57+
# async possibility, only allow one thread at a time
58+
key = f"patient_list_update_patient:{patient_id}"
59+
with TimeoutLock(key, expires=60, timeout=60):
60+
patient = PatientList.query.get(patient_id)
61+
new_record = False
62+
if not patient:
63+
new_record = True
64+
patient = PatientList(userid=patient_id)
65+
db.session.add(patient)
6266

63-
if research_study_id is None or new_record:
64-
patient.study_id = user.external_study_id
65-
patient.firstname = user.first_name
66-
patient.lastname = user.last_name
67-
patient.email = user.email
68-
patient.birthdate = user.birthdate
69-
patient.deleted = user.deleted_id is not None
70-
patient.test_role = True if user.has_role(ROLE.TEST.value) else False
71-
patient.org_id = user.organizations[0].id if user.organizations else None
72-
patient.org_name = user.organizations[0].name if user.organizations else None
67+
if research_study_id is None or new_record:
68+
patient.study_id = user.external_study_id
69+
patient.firstname = user.first_name
70+
patient.lastname = user.last_name
71+
patient.email = user.email
72+
patient.birthdate = user.birthdate
73+
patient.deleted = user.deleted_id is not None
74+
patient.test_role = True if user.has_role(ROLE.TEST.value) else False
75+
patient.org_id = user.organizations[0].id if user.organizations else None
76+
patient.org_name = user.organizations[0].name if user.organizations else None
7377

74-
# necessary to avoid recursive loop via some update paths
75-
now = datetime.utcnow()
76-
if patient.last_updated and patient.last_updated + timedelta(seconds=10) > now:
77-
db.session.commit()
78-
return
78+
# necessary to avoid recursive loop via some update paths
79+
now = datetime.utcnow()
80+
if patient.last_updated and patient.last_updated + timedelta(seconds=10) > now:
81+
db.session.commit()
82+
return
7983

80-
patient.last_updated = now
81-
if research_study_id == BASE_RS_ID or research_study_id is None:
82-
rs_id = BASE_RS_ID
83-
qb_status = qb_status_visit_name(
84-
patient.userid, research_study_id=rs_id, as_of_date=now)
85-
patient.questionnaire_status = str(qb_status['status'])
86-
patient.visit = qb_status['visit_name']
87-
patient.consentdate, _ = consent_withdrawal_dates(user=user, research_study_id=rs_id)
84+
patient.last_updated = now
85+
if research_study_id == BASE_RS_ID or research_study_id is None:
86+
rs_id = BASE_RS_ID
87+
qb_status = qb_status_visit_name(
88+
patient.userid, research_study_id=rs_id, as_of_date=now)
89+
patient.questionnaire_status = str(qb_status['status'])
90+
patient.visit = qb_status['visit_name']
91+
patient.consentdate, _ = consent_withdrawal_dates(user=user, research_study_id=rs_id)
8892

89-
if (research_study_id == EMPRO_RS_ID or research_study_id is None) and user.clinicians:
90-
rs_id = EMPRO_RS_ID
91-
patient.clinician = '; '.join(
92-
(clinician_name_map().get(c.id, "not in map") for c in user.clinicians)) or ""
93-
qb_status = qb_status_visit_name(
94-
patient.userid, research_study_id=rs_id, as_of_date=now)
95-
patient.empro_status = str(qb_status['status'])
96-
patient.empro_visit = qb_status['visit_name']
97-
patient.action_state = qb_status['action_state'].title() \
98-
if qb_status['action_state'] else ""
99-
patient.empro_consentdate, _ = consent_withdrawal_dates(
100-
user=user, research_study_id=rs_id)
101-
db.session.commit()
93+
if (research_study_id == EMPRO_RS_ID or research_study_id is None) and user.clinicians:
94+
rs_id = EMPRO_RS_ID
95+
patient.clinician = '; '.join(
96+
(clinician_name_map().get(c.id, "not in map") for c in user.clinicians)) or ""
97+
qb_status = qb_status_visit_name(
98+
patient.userid, research_study_id=rs_id, as_of_date=now)
99+
patient.empro_status = str(qb_status['status'])
100+
patient.empro_visit = qb_status['visit_name']
101+
patient.action_state = qb_status['action_state'].title() \
102+
if qb_status['action_state'] else ""
103+
patient.empro_consentdate, _ = consent_withdrawal_dates(
104+
user=user, research_study_id=rs_id)
105+
db.session.commit()

portal/models/qb_status.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ def _sync_timeline(self):
4949

5050
completed = QBT.query.filter(QBT.user_id == self.user.id).filter(
5151
QBT.research_study_id == self.research_study_id).filter(
52-
QBT.status == OverallStatus.completed).count()
53-
self.at_least_one_completed = completed > 0
52+
QBT.status == OverallStatus.completed).with_entities(QBT.id).first()
53+
self.at_least_one_completed = completed is not None
5454

5555
# Obtain withdrawal date if applicable
5656
withdrawn = QBT.query.filter(QBT.user_id == self.user.id).filter(

portal/static/js/src/admin.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1323,7 +1323,8 @@ let requestTimerId = 0;
13231323
) {
13241324
var tnthAjax = this.getDependency("tnthAjax");
13251325
tableName = tableName || this.tableIdentifier;
1326-
if (!tableName) {
1326+
if (!tableName || !document.querySelector("#adminTable")) {
1327+
if (callback) callback();
13271328
return false;
13281329
}
13291330
userId = userId || this.userId;

portal/static/js/src/modules/TnthAjax.js

+10
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ export default { /*global $ */
9393
$.ajax("/api/me").done(
9494
function() {
9595
console.log("user authorized");
96+
if ((typeof CsrfTokenChecker !== "undefined") &&
97+
!CsrfTokenChecker.checkTokenValidity()) {
98+
//if CSRF Token not valid, return error
99+
if (callback) {
100+
callback({"error": DEFAULT_SERVER_DATA_ERROR});
101+
fieldHelper.showError(targetField);
102+
}
103+
return;
104+
}
105+
96106
ajaxCall();
97107
}
98108
).fail(function() {

portal/templates/admin/admin_base.html

+2
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@
9393
// custom ajax request here
9494
function patientDataAjaxRequest(params) {
9595
loadIntervalId = setInterval(() => {
96+
//document DOM not ready, don't make ajax call yet
97+
if (!document.querySelector("#adminTable")) return;
9698
if (typeof window.AdminObj === "undefined") return;
9799
window.AdminObj.getRemotePatientListData(params);
98100
clearInterval(loadIntervalId);

portal/views/patients.py

+4
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ def filter_query(query, filter_field, filter_value):
109109
# ignore requests to filter by unknown column
110110
return query
111111

112+
if filter_field in ('birthdate', 'consentdate', 'empro_consentdate'):
113+
# these are not filterable (partial strings on date complexity) - ignore such a request
114+
return query
115+
112116
if filter_field == 'userid':
113117
query = query.filter(PatientList.userid == int(filter_value))
114118
return query

requirements.dev.txt

-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ pyparsing==2.4.7 # via packaging
2020
pytest >= 6.1.0
2121
pytest-flask==0.15.1
2222
pytest-timeout==1.4.2
23-
selenium==3.141.0
2423
snowballstemmer==2.0.0 # via sphinx
2524
sphinx-rtd-theme==0.5.1
2625
sphinx==3.3.1
@@ -36,5 +35,4 @@ virtualenv==20.4.2 # via tox
3635
waitress==1.4.3 # via webtest
3736
webob==1.8.5 # via webtest
3837
webtest==2.0.33 # via flask-webtest
39-
xvfbwrapper==0.2.9
4038
--editable .

tests/integration_tests/__init__.py

-141
This file was deleted.

tests/integration_tests/pages.py

-8
This file was deleted.

tests/integration_tests/test_login.py

-64
This file was deleted.

0 commit comments

Comments
 (0)