Skip to content

Commit

Permalink
Revert "TN-3324 robust handling of unsortable patient list attributes (
Browse files Browse the repository at this point in the history
…#4414)"

This reverts commit 212e244.
  • Loading branch information
pbugni authored Oct 24, 2024
1 parent 212e244 commit b8a4bd6
Show file tree
Hide file tree
Showing 11 changed files with 264 additions and 70 deletions.
88 changes: 42 additions & 46 deletions portal/models/patient_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,53 +53,49 @@ def patient_list_update_patient(patient_id, research_study_id=None):
if not user.has_role(ROLE.PATIENT.value):
return

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)
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).with_entities(QBT.id).first()
self.at_least_one_completed = completed is not None
QBT.status == OverallStatus.completed).count()
self.at_least_one_completed = completed > 0

# Obtain withdrawal date if applicable
withdrawn = QBT.query.filter(QBT.user_id == self.user.id).filter(
Expand Down
3 changes: 1 addition & 2 deletions portal/static/js/src/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -1323,8 +1323,7 @@ let requestTimerId = 0;
) {
var tnthAjax = this.getDependency("tnthAjax");
tableName = tableName || this.tableIdentifier;
if (!tableName || !document.querySelector("#adminTable")) {
if (callback) callback();
if (!tableName) {
return false;
}
userId = userId || this.userId;
Expand Down
10 changes: 0 additions & 10 deletions portal/static/js/src/modules/TnthAjax.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,6 @@ 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: 0 additions & 2 deletions portal/templates/admin/admin_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,6 @@
// 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: 0 additions & 4 deletions portal/views/patients.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,6 @@ 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: 2 additions & 0 deletions requirements.dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ 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 @@ -35,4 +36,5 @@ 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: 141 additions & 0 deletions tests/integration_tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
"""Unit test module for Selenium testing"""

import os
import sys
import unittest

from flask_testing import LiveServerTestCase
import pytest
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
import xvfbwrapper

from tests import TestCase


@unittest.skipUnless(
(
"SAUCE_USERNAME" in os.environ or
xvfbwrapper.Xvfb().xvfb_exists()
),
"Xvfb not installed"
)
class IntegrationTestCase(TestCase, LiveServerTestCase):
"""Test class for UI integration/workflow testing"""

def setUp(self):
"""Reset all tables before testing."""

if "SAUCE_USERNAME" in os.environ:
# Configure driver for Sauce Labs
# Presumes tunnel setup by Sauce Connect
# On TravisCI, Sauce Connect tunnel setup by Sauce Labs addon
# https://docs.travis-ci.com/user/sauce-connect
platform = {
"browserName": "firefox",
"platform": "Windows 10",
"version": "60.0",
}
capabilities = {
"tunnel-identifier": os.environ["TRAVIS_JOB_NUMBER"],
"extendedDebugging": "true",
}
metadata = {
"name": self.id(),
"build": "#%s %s" % (
os.environ["TRAVIS_BUILD_NUMBER"],
os.environ["TRAVIS_BRANCH"],
),
"tags": [
"py" + os.environ["TRAVIS_PYTHON_VERSION"],
"CI",
],
"passed": False,
}
capabilities.update(platform)
capabilities.update(metadata)

url = "http://{user}:{access_key}@localhost:4445/wd/hub".format(
user=os.environ["SAUCE_USERNAME"],
access_key=os.environ["SAUCE_ACCESS_KEY"],
)

self.driver = webdriver.Remote(
desired_capabilities=capabilities,
command_executor=url
)

else:
if "DISPLAY" not in os.environ:
# Non-graphical environment; use xvfb
self.xvfb = xvfbwrapper.Xvfb()
self.addCleanup(self.xvfb.stop)
self.xvfb.start()
self.driver = webdriver.Firefox(timeout=60)

self.addCleanup(self.driver.quit)

self.driver.root_uri = self.get_server_url()
self.driver.implicitly_wait(30)
self.verificationErrors = []
# default explicit wait time; use with Expected Conditions as needed
self.wait = WebDriverWait(self.driver, 60)
self.accept_next_alert = True

super(IntegrationTestCase, self).setUp()

def is_element_present(self, how, what):
"""Detects whether or not an element can be found in DOM
This function was exported from Selenium IDE
"""
try:
self.driver.find_element(by=how, value=what)
except NoSuchElementException as e:
return False
return True

def is_alert_present(self):
"""Detects whether an alert message is present
This function was exported from Selenium IDE
"""
try:
self.driver.switch_to_alert()
except NoAlertPresentException as e:
return False
return True

def close_alert_and_get_its_text(self):
"""Closes an alert, if present, and returns its text
If an alert is not present a NoAlertPresentException
will be thrown.
This function was exported from Selenium IDE
"""
try:
alert = self.driver.switch_to_alert()
alert_text = alert.text
if self.accept_next_alert:
alert.accept()
else:
alert.dismiss()
return alert_text
finally:
self.accept_next_alert = True

def tearDown(self):
"""Clean db session, drop all tables."""

# Update job result metadata on Sauce Labs, if available
if (
"SAUCE_USERNAME" in os.environ and

# No exception being handled - test completed successfully
sys.exc_info() == (None, None, None)
):
self.driver.execute_script("sauce:job-result=passed")

self.assertEqual([], self.verificationErrors)

super(IntegrationTestCase, self).tearDown()
8 changes: 8 additions & 0 deletions tests/integration_tests/pages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from page_objects import PageElement, PageObject


class LoginPage(PageObject):
username = PageElement(name='email')
password = PageElement(name='password')
login_button = PageElement(css='input[type="submit"]')
facebook_button = PageElement(css='.btn-facebook')
64 changes: 64 additions & 0 deletions tests/integration_tests/test_login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from flask import url_for
import pytest
from selenium.webdriver.support.ui import Select

from tests import DEFAULT_PASSWORD, TEST_USERNAME
from tests.integration_tests import IntegrationTestCase


class TestLogin(IntegrationTestCase):
"""Test class for Login integration tests"""

def test_login_page(self):
"""Ensure login works properly"""
driver = self.driver
driver.get(url_for("user.login", _external=True))

driver.find_element_by_name("email").click()
driver.find_element_by_name("email").clear()
driver.find_element_by_name("email").send_keys(TEST_USERNAME)
driver.find_element_by_name("password").click()
driver.find_element_by_name("password").clear()
driver.find_element_by_name("password").send_keys(DEFAULT_PASSWORD)
driver.find_element_by_xpath(
"//input[@class='btn btn-tnth-primary btn-lg' and @value='LOG IN']"
).click()
driver.find_element_by_id("tnthUserBtn").click()
driver.find_element_by_link_text("Log Out of TrueNTH").click()

@pytest.mark.skip(reason="not able to complete consent page")
def test_consent_after_login(self):
driver = self.driver
driver.get(url_for("eproms.home", _external=True))
driver.find_element_by_id("email").click()
driver.find_element_by_id("email").clear()
driver.find_element_by_id("email").send_keys(TEST_USERNAME)
driver.find_element_by_id("password").click()
driver.find_element_by_id("password").clear()
driver.find_element_by_id("password").send_keys(DEFAULT_PASSWORD)
driver.find_element_by_id("btnLogin").click()
driver.find_element_by_xpath("(.//*[normalize-space(text()) and normalize-space(.)='General Terms of Use'])[1]/following::i[1]").click()
driver.find_element_by_xpath("(.//*[normalize-space(text()) and normalize-space(.)='General Terms of Use'])[1]/following::i[2]").click()
driver.find_element_by_id("next").click()
driver.find_element_by_id("firstname").clear()
driver.find_element_by_id("firstname").send_keys("Test")
driver.find_element_by_id("lastname").click()
driver.find_element_by_id("lastname").clear()
driver.find_element_by_id("lastname").send_keys("User")
driver.find_element_by_id("date").click()
driver.find_element_by_id("month").click()
driver.find_element_by_xpath("(.//*[normalize-space(text()) and normalize-space(.)='(optional)'])[1]/following::option[11]").click()
driver.find_element_by_id("year").click()
driver.find_element_by_id("year").clear()
driver.find_element_by_id("year").send_keys("1988")
driver.find_element_by_id("role_patient").click()
driver.find_element_by_id("next").click()
driver.find_element_by_id("biopsy_no").click()
driver.find_element_by_id("next").click()
driver.find_element_by_id("stateSelector").click()
Select(driver.find_element_by_id("stateSelector")).select_by_visible_text("Washington")
driver.find_element_by_xpath("(.//*[normalize-space(text()) and normalize-space(.)='Your clinic of care.'])[1]/following::option[12]").click()
driver.find_element_by_xpath("(.//*[normalize-space(text()) and normalize-space(.)='UW Medicine (University of Washington)'])[1]/following::label[1]").click()
driver.find_element_by_id("updateProfile").click()
driver.find_element_by_id("tnthUserBtn").click()
driver.find_element_by_link_text("Log Out of TrueNTH").click()
Loading

0 comments on commit b8a4bd6

Please sign in to comment.