Skip to content

Commit

Permalink
TN-3324 robust handling of unsortable patient list attributes (#4414)
Browse files Browse the repository at this point in the history
Co-authored-by: Ivan Cvitkovic <[email protected]>
Co-authored-by: Amy Chen <[email protected]>
Co-authored-by: Amy Chen <[email protected]>
  • Loading branch information
4 people authored Oct 24, 2024
1 parent ffa7df7 commit 212e244
Show file tree
Hide file tree
Showing 11 changed files with 70 additions and 264 deletions.
88 changes: 46 additions & 42 deletions portal/models/patient_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,49 +53,53 @@ def patient_list_update_patient(patient_id, research_study_id=None):
if not user.has_role(ROLE.PATIENT.value):
return

patient = PatientList.query.get(patient_id)
new_record = False
if not patient:
new_record = True
patient = PatientList(userid=patient_id)
db.session.add(patient)
from ..timeout_lock import TimeoutLock
# async possibility, only allow one thread at a time
key = f"patient_list_update_patient:{patient_id}"
with TimeoutLock(key, expires=60, timeout=60):
patient = PatientList.query.get(patient_id)
new_record = False
if not patient:
new_record = True
patient = PatientList(userid=patient_id)
db.session.add(patient)

if research_study_id is None or new_record:
patient.study_id = user.external_study_id
patient.firstname = user.first_name
patient.lastname = user.last_name
patient.email = user.email
patient.birthdate = user.birthdate
patient.deleted = user.deleted_id is not None
patient.test_role = True if user.has_role(ROLE.TEST.value) else False
patient.org_id = user.organizations[0].id if user.organizations else None
patient.org_name = user.organizations[0].name if user.organizations else None
if research_study_id is None or new_record:
patient.study_id = user.external_study_id
patient.firstname = user.first_name
patient.lastname = user.last_name
patient.email = user.email
patient.birthdate = user.birthdate
patient.deleted = user.deleted_id is not None
patient.test_role = True if user.has_role(ROLE.TEST.value) else False
patient.org_id = user.organizations[0].id if user.organizations else None
patient.org_name = user.organizations[0].name if user.organizations else None

# necessary to avoid recursive loop via some update paths
now = datetime.utcnow()
if patient.last_updated and patient.last_updated + timedelta(seconds=10) > now:
db.session.commit()
return
# necessary to avoid recursive loop via some update paths
now = datetime.utcnow()
if patient.last_updated and patient.last_updated + timedelta(seconds=10) > now:
db.session.commit()
return

patient.last_updated = now
if research_study_id == BASE_RS_ID or research_study_id is None:
rs_id = BASE_RS_ID
qb_status = qb_status_visit_name(
patient.userid, research_study_id=rs_id, as_of_date=now)
patient.questionnaire_status = str(qb_status['status'])
patient.visit = qb_status['visit_name']
patient.consentdate, _ = consent_withdrawal_dates(user=user, research_study_id=rs_id)
patient.last_updated = now
if research_study_id == BASE_RS_ID or research_study_id is None:
rs_id = BASE_RS_ID
qb_status = qb_status_visit_name(
patient.userid, research_study_id=rs_id, as_of_date=now)
patient.questionnaire_status = str(qb_status['status'])
patient.visit = qb_status['visit_name']
patient.consentdate, _ = consent_withdrawal_dates(user=user, research_study_id=rs_id)

if (research_study_id == EMPRO_RS_ID or research_study_id is None) and user.clinicians:
rs_id = EMPRO_RS_ID
patient.clinician = '; '.join(
(clinician_name_map().get(c.id, "not in map") for c in user.clinicians)) or ""
qb_status = qb_status_visit_name(
patient.userid, research_study_id=rs_id, as_of_date=now)
patient.empro_status = str(qb_status['status'])
patient.empro_visit = qb_status['visit_name']
patient.action_state = qb_status['action_state'].title() \
if qb_status['action_state'] else ""
patient.empro_consentdate, _ = consent_withdrawal_dates(
user=user, research_study_id=rs_id)
db.session.commit()
if (research_study_id == EMPRO_RS_ID or research_study_id is None) and user.clinicians:
rs_id = EMPRO_RS_ID
patient.clinician = '; '.join(
(clinician_name_map().get(c.id, "not in map") for c in user.clinicians)) or ""
qb_status = qb_status_visit_name(
patient.userid, research_study_id=rs_id, as_of_date=now)
patient.empro_status = str(qb_status['status'])
patient.empro_visit = qb_status['visit_name']
patient.action_state = qb_status['action_state'].title() \
if qb_status['action_state'] else ""
patient.empro_consentdate, _ = consent_withdrawal_dates(
user=user, research_study_id=rs_id)
db.session.commit()
4 changes: 2 additions & 2 deletions portal/models/qb_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ def _sync_timeline(self):

completed = QBT.query.filter(QBT.user_id == self.user.id).filter(
QBT.research_study_id == self.research_study_id).filter(
QBT.status == OverallStatus.completed).count()
self.at_least_one_completed = completed > 0
QBT.status == OverallStatus.completed).with_entities(QBT.id).first()
self.at_least_one_completed = completed is not None

# Obtain withdrawal date if applicable
withdrawn = QBT.query.filter(QBT.user_id == self.user.id).filter(
Expand Down
3 changes: 2 additions & 1 deletion portal/static/js/src/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -1323,7 +1323,8 @@ let requestTimerId = 0;
) {
var tnthAjax = this.getDependency("tnthAjax");
tableName = tableName || this.tableIdentifier;
if (!tableName) {
if (!tableName || !document.querySelector("#adminTable")) {
if (callback) callback();
return false;
}
userId = userId || this.userId;
Expand Down
10 changes: 10 additions & 0 deletions portal/static/js/src/modules/TnthAjax.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ export default { /*global $ */
$.ajax("/api/me").done(
function() {
console.log("user authorized");
if ((typeof CsrfTokenChecker !== "undefined") &&
!CsrfTokenChecker.checkTokenValidity()) {
//if CSRF Token not valid, return error
if (callback) {
callback({"error": DEFAULT_SERVER_DATA_ERROR});
fieldHelper.showError(targetField);
}
return;
}

ajaxCall();
}
).fail(function() {
Expand Down
2 changes: 2 additions & 0 deletions portal/templates/admin/admin_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@
// custom ajax request here
function patientDataAjaxRequest(params) {
loadIntervalId = setInterval(() => {
//document DOM not ready, don't make ajax call yet
if (!document.querySelector("#adminTable")) return;
if (typeof window.AdminObj === "undefined") return;
window.AdminObj.getRemotePatientListData(params);
clearInterval(loadIntervalId);
Expand Down
4 changes: 4 additions & 0 deletions portal/views/patients.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ def filter_query(query, filter_field, filter_value):
# ignore requests to filter by unknown column
return query

if filter_field in ('birthdate', 'consentdate', 'empro_consentdate'):
# these are not filterable (partial strings on date complexity) - ignore such a request
return query

if filter_field == 'userid':
query = query.filter(PatientList.userid == int(filter_value))
return query
Expand Down
2 changes: 0 additions & 2 deletions requirements.dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ pyparsing==2.4.7 # via packaging
pytest >= 6.1.0
pytest-flask==0.15.1
pytest-timeout==1.4.2
selenium==3.141.0
snowballstemmer==2.0.0 # via sphinx
sphinx-rtd-theme==0.5.1
sphinx==3.3.1
Expand All @@ -36,5 +35,4 @@ virtualenv==20.4.2 # via tox
waitress==1.4.3 # via webtest
webob==1.8.5 # via webtest
webtest==2.0.33 # via flask-webtest
xvfbwrapper==0.2.9
--editable .
141 changes: 0 additions & 141 deletions tests/integration_tests/__init__.py

This file was deleted.

8 changes: 0 additions & 8 deletions tests/integration_tests/pages.py

This file was deleted.

64 changes: 0 additions & 64 deletions tests/integration_tests/test_login.py

This file was deleted.

Loading

0 comments on commit 212e244

Please sign in to comment.