mirror of
https://github.com/sartography/cr-connect-workflow.git
synced 2025-02-22 20:58:28 +00:00
Merge pull request #14 from sartography/feature/protocol-builder
Feature/protocol builder
This commit is contained in:
commit
cd07d9d95e
1
Pipfile
1
Pipfile
@ -33,6 +33,7 @@ recommonmark = "*"
|
||||
psycopg2-binary = "*"
|
||||
docxtpl = "*"
|
||||
flask-sso = "*"
|
||||
python-dateutil = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
||||
|
35
Pipfile.lock
generated
35
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "4bcfdceb9683a7885711d4d96296d8ba78fd60bebd5ab56cd3bb1b5eb1dee7b2"
|
||||
"sha256": "60e48d05048f627878a5c81377318bc2ff2a94b2574441c166fda3a523c789df"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
@ -91,10 +91,10 @@
|
||||
},
|
||||
"billiard": {
|
||||
"hashes": [
|
||||
"sha256:26fd494dc3251f8ce1f5559744f18aeed427fdaf29a75d7baae26752a5d3816f",
|
||||
"sha256:f4e09366653aa3cb3ae8ed16423f9ba1665ff426f087bcdbbed86bf3664fe02c"
|
||||
"sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede",
|
||||
"sha256:d91725ce6425f33a97dfa72fb6bfef0e47d4652acd98a032bd1a7fbf06d5fa6a"
|
||||
],
|
||||
"version": "==3.6.2.0"
|
||||
"version": "==3.6.3.0"
|
||||
},
|
||||
"blinker": {
|
||||
"hashes": [
|
||||
@ -597,6 +597,7 @@
|
||||
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
|
||||
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.8.1"
|
||||
},
|
||||
"python-docx": {
|
||||
@ -668,18 +669,18 @@
|
||||
},
|
||||
"soupsieve": {
|
||||
"hashes": [
|
||||
"sha256:bdb0d917b03a1369ce964056fc195cfdff8819c40de04695a80bc813c3cfa1f5",
|
||||
"sha256:e2c1c5dee4a1c36bcb790e0fabd5492d874b8ebd4617622c4f6a731701060dda"
|
||||
"sha256:e914534802d7ffd233242b785229d5ba0766a7f487385e3f714446a07bf540ae",
|
||||
"sha256:fcd71e08c0aee99aca1b73f45478549ee7e7fc006d51b37bec9e9def7dc22b69"
|
||||
],
|
||||
"version": "==1.9.5"
|
||||
"version": "==2.0"
|
||||
},
|
||||
"sphinx": {
|
||||
"hashes": [
|
||||
"sha256:525527074f2e0c2585f68f73c99b4dc257c34bbe308b27f5f8c7a6e20642742f",
|
||||
"sha256:543d39db5f82d83a5c1aa0c10c88f2b6cff2da3e711aa849b2c627b4b403bbd9"
|
||||
"sha256:776ff8333181138fae52df65be733127539623bb46cc692e7fa0fcfc80d7aa88",
|
||||
"sha256:ca762da97c3b5107cbf0ab9e11d3ec7ab8d3c31377266fd613b962ed971df709"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.4.2"
|
||||
"version": "==2.4.3"
|
||||
},
|
||||
"sphinxcontrib-applehelp": {
|
||||
"hashes": [
|
||||
@ -697,10 +698,10 @@
|
||||
},
|
||||
"sphinxcontrib-htmlhelp": {
|
||||
"hashes": [
|
||||
"sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422",
|
||||
"sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"
|
||||
"sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f",
|
||||
"sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"
|
||||
],
|
||||
"version": "==1.0.2"
|
||||
"version": "==1.0.3"
|
||||
},
|
||||
"sphinxcontrib-jsmath": {
|
||||
"hashes": [
|
||||
@ -726,7 +727,7 @@
|
||||
"spiffworkflow": {
|
||||
"editable": true,
|
||||
"git": "https://github.com/sartography/SpiffWorkflow.git",
|
||||
"ref": "bab9a280f94b8a94ade5107c0bce104aade9ad04"
|
||||
"ref": "0da57a83bfa0edaf1cfd5500f87757553621c412"
|
||||
},
|
||||
"sqlalchemy": {
|
||||
"hashes": [
|
||||
@ -788,11 +789,11 @@
|
||||
},
|
||||
"xlsxwriter": {
|
||||
"hashes": [
|
||||
"sha256:18fe8f891a4adf7556c05d56059e136f9fbce5b19f9335f6d7b42c389c4592bc",
|
||||
"sha256:5d3630ff9b2a277c939bd5053d0e7466499593abebbab9ce1dc9b1481a8ebbb6"
|
||||
"sha256:488e1988ab16ff3a9cd58c7656d0a58f8abe46ee58b98eecea78c022db28656b",
|
||||
"sha256:97ab487b81534415c5313154203f3e8a637d792b1e6a8201e8f7f71da0203c2a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.2.7"
|
||||
"version": "==1.2.8"
|
||||
},
|
||||
"zipp": {
|
||||
"hashes": [
|
||||
|
@ -19,7 +19,7 @@ auth = HTTPTokenAuth('Bearer')
|
||||
|
||||
if "TESTING" in os.environ and os.environ["TESTING"] == "true":
|
||||
app.config.from_object('config.testing')
|
||||
app.config.from_pyfile('testing.py')
|
||||
app.config.from_pyfile('../config/testing.py')
|
||||
else:
|
||||
app.config.root_path = app.instance_path
|
||||
app.config.from_pyfile('config.py', silent=True)
|
||||
|
@ -670,6 +670,9 @@ components:
|
||||
type: string
|
||||
enum: [out_of_date, in_process, complete, updating]
|
||||
example: done
|
||||
user_uid:
|
||||
type: string
|
||||
example: dhf8r
|
||||
primary_investigator_id:
|
||||
type: string
|
||||
example: dhf8r
|
||||
|
@ -8,7 +8,7 @@ from flask import send_file
|
||||
from crc import session
|
||||
from crc.api.common import ApiErrorSchema, ApiError
|
||||
from crc.models.file import FileModelSchema, FileModel, FileDataModel, FileType
|
||||
from crc.services.FileService import FileService
|
||||
from crc.services.file_service import FileService
|
||||
|
||||
|
||||
def get_files(workflow_spec_id=None, study_id=None, workflow_id=None, task_id=None, form_field_key=None):
|
||||
|
114
crc/api/study.py
114
crc/api/study.py
@ -1,29 +1,36 @@
|
||||
from connexion import NoContent
|
||||
from typing import List, Optional, Union, Tuple, Dict
|
||||
|
||||
from crc import session
|
||||
from connexion import NoContent
|
||||
from flask import g
|
||||
|
||||
from crc import session, auth
|
||||
from crc.api.common import ApiError, ApiErrorSchema
|
||||
from crc.api.workflow import __get_workflow_api_model
|
||||
from crc.models.protocol_builder import ProtocolBuilderStatus, ProtocolBuilderStudy
|
||||
from crc.models.study import StudyModelSchema, StudyModel
|
||||
from crc.models.workflow import WorkflowModel, WorkflowApiSchema, WorkflowSpecModel
|
||||
from crc.models.workflow import WorkflowModel, WorkflowApiSchema, WorkflowSpecModel, WorkflowApi
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
from crc.services.protocol_builder import ProtocolBuilderService
|
||||
|
||||
|
||||
@auth.login_required
|
||||
def all_studies():
|
||||
# todo: Limit returned studies to a user
|
||||
schema = StudyModelSchema(many=True)
|
||||
return schema.dump(session.query(StudyModel).all())
|
||||
return update_from_protocol_builder()
|
||||
|
||||
|
||||
@auth.login_required
|
||||
def add_study(body):
|
||||
study = StudyModelSchema().load(body, session=session)
|
||||
session.add(study)
|
||||
session.commit()
|
||||
|
||||
# FIXME: We need to ask the protocol builder what workflows to add to the study, not just add them all.
|
||||
for spec in session.query(WorkflowSpecModel).all():
|
||||
WorkflowProcessor.create(study.id, spec.id)
|
||||
return StudyModelSchema().dump(study)
|
||||
|
||||
|
||||
@auth.login_required
|
||||
def update_study(study_id, body):
|
||||
if study_id is None:
|
||||
error = ApiError('unknown_study', 'Please provide a valid Study ID.')
|
||||
@ -35,12 +42,14 @@ def update_study(study_id, body):
|
||||
error = ApiError('unknown_study', 'The study "' + study_id + '" is not recognized.')
|
||||
return ApiErrorSchema.dump(error), 404
|
||||
|
||||
study = StudyModelSchema().load(body, session=session, instance=study)
|
||||
schema = StudyModelSchema()
|
||||
study = schema.load(body, session=session, instance=study, partial=True)
|
||||
session.add(study)
|
||||
session.commit()
|
||||
return StudyModelSchema().dump(study)
|
||||
return schema.dump(study)
|
||||
|
||||
|
||||
@auth.login_required
|
||||
def get_study(study_id):
|
||||
study = session.query(StudyModel).filter_by(id=study_id).first()
|
||||
schema = StudyModelSchema()
|
||||
@ -48,28 +57,71 @@ def get_study(study_id):
|
||||
return NoContent, 404
|
||||
return schema.dump(study)
|
||||
|
||||
def update_from_protocol_builder():
|
||||
"""Call the """
|
||||
|
||||
def post_update_study_from_protocol_builder(study_id):
|
||||
"""This will update the list of known studies based on data received from
|
||||
@auth.login_required
|
||||
def update_from_protocol_builder():
|
||||
"""Updates the list of known studies for a given user based on data received from
|
||||
the protocol builder."""
|
||||
|
||||
# todo: Actually get data from an external service here
|
||||
user = g.user
|
||||
""":type: crc.models.user.UserModel"""
|
||||
|
||||
# Get studies matching this user from Protocol Builder
|
||||
pb_studies: List[ProtocolBuilderStudy] = get_user_pb_studies()
|
||||
|
||||
# Get studies from the database
|
||||
db_studies = session.query(StudyModel).filter_by(user_uid=user.uid).all()
|
||||
db_study_ids = list(map(lambda s: s.id, db_studies))
|
||||
pb_study_ids = list(map(lambda s: s['STUDYID'], pb_studies))
|
||||
|
||||
for pb_study in pb_studies:
|
||||
|
||||
# Update studies with latest data from Protocol Builder
|
||||
if pb_study['STUDYID'] in db_study_ids:
|
||||
update_study(pb_study['STUDYID'], map_pb_study_to_study(pb_study))
|
||||
|
||||
# Add studies from Protocol Builder that aren't in the database yet
|
||||
else:
|
||||
new_study = map_pb_study_to_study(pb_study)
|
||||
add_study(new_study)
|
||||
|
||||
# Mark studies as inactive that are no longer in Protocol Builder
|
||||
for study_id in db_study_ids:
|
||||
if study_id not in pb_study_ids:
|
||||
update_study(study_id=study_id, body={'inactive': True})
|
||||
|
||||
# Return updated studies
|
||||
updated_studies = session.query(StudyModel).filter_by(user_uid=user.uid).all()
|
||||
results = StudyModelSchema(many=True).dump(updated_studies)
|
||||
return results
|
||||
|
||||
|
||||
@auth.login_required
|
||||
def post_update_study_from_protocol_builder(study_id):
|
||||
"""Update a single study based on data received from
|
||||
the protocol builder."""
|
||||
|
||||
pb_studies: List[ProtocolBuilderStudy] = get_user_pb_studies()
|
||||
for pb_study in pb_studies:
|
||||
if pb_study['STUDYID'] == study_id:
|
||||
return update_study(study_id, map_pb_study_to_study(pb_study))
|
||||
|
||||
return NoContent, 304
|
||||
|
||||
|
||||
@auth.login_required
|
||||
def get_study_workflows(study_id):
|
||||
workflow_models = session.query(WorkflowModel).filter_by(study_id=study_id).all()
|
||||
api_models = []
|
||||
for workflow_model in workflow_models:
|
||||
processor = WorkflowProcessor(workflow_model.workflow_spec_id,
|
||||
workflow_model.bpmn_workflow_json)
|
||||
api_models.append( __get_workflow_api_model(processor))
|
||||
api_models.append(__get_workflow_api_model(processor))
|
||||
schema = WorkflowApiSchema(many=True)
|
||||
return schema.dump(api_models)
|
||||
|
||||
|
||||
@auth.login_required
|
||||
def add_workflow_to_study(study_id, body):
|
||||
workflow_spec_model = session.query(WorkflowSpecModel).filter_by(id=body["id"]).first()
|
||||
if workflow_spec_model is None:
|
||||
@ -78,3 +130,37 @@ def add_workflow_to_study(study_id, body):
|
||||
processor = WorkflowProcessor.create(study_id, workflow_spec_model.id)
|
||||
return WorkflowApiSchema().dump(__get_workflow_api_model(processor))
|
||||
|
||||
|
||||
@auth.login_required
|
||||
def get_user_pb_studies() -> List[ProtocolBuilderStudy]:
|
||||
"""Get studies from Protocol Builder matching the given user"""
|
||||
|
||||
user = g.user
|
||||
""":type: crc.models.user.UserModel"""
|
||||
|
||||
return ProtocolBuilderService.get_studies(user.uid)
|
||||
|
||||
|
||||
def map_pb_study_to_study(pb_study):
|
||||
"""Translates the given dict of ProtocolBuilderStudy properties to dict of StudyModel attributes"""
|
||||
prop_map = {
|
||||
'STUDYID': 'id',
|
||||
'HSRNUMBER': 'hsr_number',
|
||||
'TITLE': 'title',
|
||||
'NETBADGEID': 'user_uid',
|
||||
'DATE_MODIFIED': 'last_updated',
|
||||
}
|
||||
study_info = {}
|
||||
|
||||
# Translate Protocol Builder property names to Study attributes
|
||||
for k, v in pb_study.items():
|
||||
if k in prop_map:
|
||||
study_info[prop_map[k]] = v
|
||||
|
||||
if pb_study['Q_COMPLETE']:
|
||||
study_info['protocol_builder_status'] = ProtocolBuilderStatus.complete._value_
|
||||
else:
|
||||
study_info['protocol_builder_status'] = ProtocolBuilderStatus.in_process._value_
|
||||
|
||||
return study_info
|
||||
|
||||
|
247
crc/models/protocol_builder.py
Normal file
247
crc/models/protocol_builder.py
Normal file
@ -0,0 +1,247 @@
|
||||
import enum
|
||||
|
||||
from marshmallow import INCLUDE
|
||||
|
||||
from crc import ma
|
||||
|
||||
|
||||
class ProtocolBuilderInvestigatorType(enum.Enum):
|
||||
PI = "Primary Investigator"
|
||||
SI = "Sub Investigator"
|
||||
DC = "Department Contact"
|
||||
SC_I = "Study Coordinator 1"
|
||||
SC_II = "Study Coordinator 2"
|
||||
AS_C = "Additional Study Coordinators"
|
||||
DEPT_CH = "Department Chair"
|
||||
IRBC = "IRB Coordinator"
|
||||
SCI = "Scientific Contact"
|
||||
|
||||
|
||||
class ProtocolBuilderStatus(enum.Enum):
|
||||
out_of_date = "out_of_date"
|
||||
in_process = "in_process"
|
||||
complete = "complete"
|
||||
updating = "updating"
|
||||
|
||||
|
||||
class ProtocolBuilderStudy(object):
|
||||
def __init__(
|
||||
self, STUDYID: int, HSRNUMBER: str, TITLE: str, NETBADGEID: str,
|
||||
Q_COMPLETE: bool, DATE_MODIFIED: str
|
||||
):
|
||||
self.STUDYID = STUDYID
|
||||
self.HSRNUMBER = HSRNUMBER
|
||||
self.TITLE = TITLE
|
||||
self.NETBADGEID = NETBADGEID
|
||||
self.Q_COMPLETE = Q_COMPLETE
|
||||
self.DATE_MODIFIED = DATE_MODIFIED
|
||||
|
||||
|
||||
class ProtocolBuilderStudySchema(ma.Schema):
|
||||
class Meta:
|
||||
model = ProtocolBuilderStudy
|
||||
unknown = INCLUDE
|
||||
|
||||
|
||||
class ProtocolBuilderInvestigator(object):
|
||||
def __init__(self, STUDYID: int, NETBADGEID: str, INVESTIGATORTYPE: str, INVESTIGATORTYPEFULL: str):
|
||||
self.STUDYID = STUDYID
|
||||
self.NETBADGEID = NETBADGEID
|
||||
self.INVESTIGATORTYPE = INVESTIGATORTYPE
|
||||
self.INVESTIGATORTYPEFULL = INVESTIGATORTYPEFULL
|
||||
|
||||
|
||||
class ProtocolBuilderInvestigatorSchema(ma.Schema):
|
||||
class Meta:
|
||||
model = ProtocolBuilderInvestigator
|
||||
unknown = INCLUDE
|
||||
|
||||
|
||||
class ProtocolBuilderRequiredDocument(object):
|
||||
DOC_TYPES = {
|
||||
1: "Investigators Brochure",
|
||||
6: "Cancer Center's PRC Approval Form",
|
||||
8: "SOM CTO IND/IDE Review Letter",
|
||||
9: "HIRE Approval",
|
||||
10: "Cancer Center's PRC Approval Waiver",
|
||||
12: "Certificate of Confidentiality Application",
|
||||
14: "Institutional Biosafety Committee Approval",
|
||||
18: "SOM CTO Approval Letter - UVA PI Multisite Trial",
|
||||
20: "IRB Approval or Letter of Approval from Administration: Study Conducted at non- UVA Facilities ",
|
||||
21: "New Medical Device Form",
|
||||
22: "SOM CTO Review regarding need for IDE",
|
||||
23: "SOM CTO Review regarding need for IND",
|
||||
24: "InfoSec Approval",
|
||||
25: "Scientific Pre-review Documentation",
|
||||
26: "IBC Number",
|
||||
32: "IDS - Investigational Drug Service Approval",
|
||||
36: "RDRC Approval ",
|
||||
40: "SBS/IRB Approval-FERPA",
|
||||
41: "HIRE Standard Radiation Language",
|
||||
42: "COI Management Plan ",
|
||||
43: "SOM CTO Approval Letter-Non UVA, Non Industry PI MultiSite Study",
|
||||
44: "GRIME Approval",
|
||||
45: "GMEC Approval",
|
||||
46: "IRB Reliance Agreement Request Form- IRB-HSR is IRB of Record",
|
||||
47: "Non UVA IRB Approval - Initial and Last Continuation",
|
||||
48: "MR Physicist Approval- Use of Gadolinium",
|
||||
49: "SOM CTO Approval- Non- UVA Academia PI of IDE",
|
||||
51: "IDS Waiver",
|
||||
52: "Package Inserts",
|
||||
53: "IRB Reliance Agreement Request Form- IRB-HSR Not IRB of Record",
|
||||
54: "ESCRO Approval",
|
||||
57: "Laser Safety Officer Approval",
|
||||
}
|
||||
|
||||
def __init__(self, AUXDOCID: str, AUXDOC: str):
|
||||
self.AUXDOCID = AUXDOCID
|
||||
self.AUXDOC = AUXDOC
|
||||
|
||||
|
||||
class ProtocolBuilderRequiredDocumentSchema(ma.Schema):
|
||||
class Meta:
|
||||
model = ProtocolBuilderRequiredDocument
|
||||
unknown = INCLUDE
|
||||
|
||||
|
||||
class ProtocolBuilderStudyDetails(object):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
STUDYID: int,
|
||||
IS_IND: int,
|
||||
IND_1: str,
|
||||
IND_2: str,
|
||||
IND_3: str,
|
||||
IS_UVA_IND: int,
|
||||
IS_IDE: int,
|
||||
IS_UVA_IDE: int,
|
||||
IDE: str,
|
||||
IS_CHART_REVIEW: int,
|
||||
IS_RADIATION: int,
|
||||
GCRC_NUMBER: str,
|
||||
IS_GCRC: int,
|
||||
IS_PRC_DSMP: int,
|
||||
IS_PRC: int,
|
||||
PRC_NUMBER: str,
|
||||
IS_IBC: int,
|
||||
IBC_NUMBER: str,
|
||||
SPONSORS_PROTOCOL_REVISION_DATE: int,
|
||||
IS_SPONSOR_MONITORING: int,
|
||||
IS_AUX: int,
|
||||
IS_SPONSOR: int,
|
||||
IS_GRANT: int,
|
||||
IS_COMMITTEE_CONFLICT: int,
|
||||
DSMB: int,
|
||||
DSMB_FREQUENCY: int,
|
||||
IS_DB: int,
|
||||
IS_UVA_DB: int,
|
||||
IS_CENTRAL_REG_DB: int,
|
||||
IS_CONSENT_WAIVER: int,
|
||||
IS_HGT: int,
|
||||
IS_GENE_TRANSFER: int,
|
||||
IS_TISSUE_BANKING: int,
|
||||
IS_SURROGATE_CONSENT: int,
|
||||
IS_ADULT_PARTICIPANT: int,
|
||||
IS_MINOR_PARTICIPANT: int,
|
||||
IS_MINOR: int,
|
||||
IS_BIOMEDICAL: int,
|
||||
IS_QUALITATIVE: int,
|
||||
IS_PI_SCHOOL: int,
|
||||
IS_PRISONERS_POP: int,
|
||||
IS_PREGNANT_POP: int,
|
||||
IS_FETUS_POP: int,
|
||||
IS_MENTAL_IMPAIRMENT_POP: int,
|
||||
IS_ELDERLY_POP: int,
|
||||
IS_OTHER_VULNERABLE_POP: int,
|
||||
OTHER_VULNERABLE_DESC: str,
|
||||
IS_MULTI_SITE: int,
|
||||
IS_UVA_LOCATION: int,
|
||||
NON_UVA_LOCATION: str,
|
||||
MULTI_SITE_LOCATIONS: str,
|
||||
IS_OUTSIDE_CONTRACT: int,
|
||||
IS_UVA_PI_MULTI: int,
|
||||
IS_NOT_PRC_WAIVER: int,
|
||||
IS_CANCER_PATIENT: int,
|
||||
UPLOAD_COMPLETE: int,
|
||||
IS_FUNDING_SOURCE: int,
|
||||
IS_PI_INITIATED: int,
|
||||
IS_ENGAGED_RESEARCH: int,
|
||||
IS_APPROVED_DEVICE: int,
|
||||
IS_FINANCIAL_CONFLICT: int,
|
||||
IS_NOT_CONSENT_WAIVER: int,
|
||||
IS_FOR_CANCER_CENTER: int,
|
||||
IS_REVIEW_BY_CENTRAL_IRB: int,
|
||||
IRBREVIEWERADMIN: str
|
||||
):
|
||||
self.STUDYID = STUDYID
|
||||
self.IS_IND = IS_IND
|
||||
self.IND_1 = IND_1
|
||||
self.IND_2 = IND_2
|
||||
self.IND_3 = IND_3
|
||||
self.IS_UVA_IND = IS_UVA_IND
|
||||
self.IS_IDE = IS_IDE
|
||||
self.IS_UVA_IDE = IS_UVA_IDE
|
||||
self.IDE = IDE
|
||||
self.IS_CHART_REVIEW = IS_CHART_REVIEW
|
||||
self.IS_RADIATION = IS_RADIATION
|
||||
self.GCRC_NUMBER = GCRC_NUMBER
|
||||
self.IS_GCRC = IS_GCRC
|
||||
self.IS_PRC_DSMP = IS_PRC_DSMP
|
||||
self.IS_PRC = IS_PRC
|
||||
self.PRC_NUMBER = PRC_NUMBER
|
||||
self.IS_IBC = IS_IBC
|
||||
self.IBC_NUMBER = IBC_NUMBER
|
||||
self.SPONSORS_PROTOCOL_REVISION_DATE = SPONSORS_PROTOCOL_REVISION_DATE
|
||||
self.IS_SPONSOR_MONITORING = IS_SPONSOR_MONITORING
|
||||
self.IS_AUX = IS_AUX
|
||||
self.IS_SPONSOR = IS_SPONSOR
|
||||
self.IS_GRANT = IS_GRANT
|
||||
self.IS_COMMITTEE_CONFLICT = IS_COMMITTEE_CONFLICT
|
||||
self.DSMB = DSMB
|
||||
self.DSMB_FREQUENCY = DSMB_FREQUENCY
|
||||
self.IS_DB = IS_DB
|
||||
self.IS_UVA_DB = IS_UVA_DB
|
||||
self.IS_CENTRAL_REG_DB = IS_CENTRAL_REG_DB
|
||||
self.IS_CONSENT_WAIVER = IS_CONSENT_WAIVER
|
||||
self.IS_HGT = IS_HGT
|
||||
self.IS_GENE_TRANSFER = IS_GENE_TRANSFER
|
||||
self.IS_TISSUE_BANKING = IS_TISSUE_BANKING
|
||||
self.IS_SURROGATE_CONSENT = IS_SURROGATE_CONSENT
|
||||
self.IS_ADULT_PARTICIPANT = IS_ADULT_PARTICIPANT
|
||||
self.IS_MINOR_PARTICIPANT = IS_MINOR_PARTICIPANT
|
||||
self.IS_MINOR = IS_MINOR
|
||||
self.IS_BIOMEDICAL = IS_BIOMEDICAL
|
||||
self.IS_QUALITATIVE = IS_QUALITATIVE
|
||||
self.IS_PI_SCHOOL = IS_PI_SCHOOL
|
||||
self.IS_PRISONERS_POP = IS_PRISONERS_POP
|
||||
self.IS_PREGNANT_POP = IS_PREGNANT_POP
|
||||
self.IS_FETUS_POP = IS_FETUS_POP
|
||||
self.IS_MENTAL_IMPAIRMENT_POP = IS_MENTAL_IMPAIRMENT_POP
|
||||
self.IS_ELDERLY_POP = IS_ELDERLY_POP
|
||||
self.IS_OTHER_VULNERABLE_POP = IS_OTHER_VULNERABLE_POP
|
||||
self.OTHER_VULNERABLE_DESC = OTHER_VULNERABLE_DESC
|
||||
self.IS_MULTI_SITE = IS_MULTI_SITE
|
||||
self.IS_UVA_LOCATION = IS_UVA_LOCATION
|
||||
self.NON_UVA_LOCATION = NON_UVA_LOCATION
|
||||
self.MULTI_SITE_LOCATIONS = MULTI_SITE_LOCATIONS
|
||||
self.IS_OUTSIDE_CONTRACT = IS_OUTSIDE_CONTRACT
|
||||
self.IS_UVA_PI_MULTI = IS_UVA_PI_MULTI
|
||||
self.IS_NOT_PRC_WAIVER = IS_NOT_PRC_WAIVER
|
||||
self.IS_CANCER_PATIENT = IS_CANCER_PATIENT
|
||||
self.UPLOAD_COMPLETE = UPLOAD_COMPLETE
|
||||
self.IS_FUNDING_SOURCE = IS_FUNDING_SOURCE
|
||||
self.IS_PI_INITIATED = IS_PI_INITIATED
|
||||
self.IS_ENGAGED_RESEARCH = IS_ENGAGED_RESEARCH
|
||||
self.IS_APPROVED_DEVICE = IS_APPROVED_DEVICE
|
||||
self.IS_FINANCIAL_CONFLICT = IS_FINANCIAL_CONFLICT
|
||||
self.IS_NOT_CONSENT_WAIVER = IS_NOT_CONSENT_WAIVER
|
||||
self.IS_FOR_CANCER_CENTER = IS_FOR_CANCER_CENTER
|
||||
self.IS_REVIEW_BY_CENTRAL_IRB = IS_REVIEW_BY_CENTRAL_IRB
|
||||
self.IRBREVIEWERADMIN = IRBREVIEWERADMIN
|
||||
|
||||
|
||||
class ProtocolBuilderStudyDetailsSchema(ma.Schema):
|
||||
class Meta:
|
||||
model = ProtocolBuilderStudyDetails
|
||||
unknown = INCLUDE
|
@ -1,17 +1,9 @@
|
||||
import enum
|
||||
|
||||
from marshmallow_enum import EnumField
|
||||
from marshmallow_sqlalchemy import ModelSchema
|
||||
from sqlalchemy import func
|
||||
|
||||
from crc import db
|
||||
|
||||
|
||||
class ProtocolBuilderStatus(enum.Enum):
|
||||
out_of_date = "out_of_date"
|
||||
in_process = "in_process"
|
||||
complete = "complete"
|
||||
updating = "updating"
|
||||
from crc.models.protocol_builder import ProtocolBuilderStatus
|
||||
|
||||
|
||||
class StudyModel(db.Model):
|
||||
@ -20,13 +12,21 @@ class StudyModel(db.Model):
|
||||
title = db.Column(db.String)
|
||||
last_updated = db.Column(db.DateTime(timezone=True), default=func.now())
|
||||
protocol_builder_status = db.Column(db.Enum(ProtocolBuilderStatus))
|
||||
primary_investigator_id = db.Column(db.String)
|
||||
sponsor = db.Column(db.String)
|
||||
ind_number = db.Column(db.String)
|
||||
primary_investigator_id = db.Column(db.String, nullable=True)
|
||||
sponsor = db.Column(db.String, nullable=True)
|
||||
hsr_number = db.Column(db.String, nullable=True)
|
||||
ind_number = db.Column(db.String, nullable=True)
|
||||
user_uid = db.Column(db.String, db.ForeignKey('user.uid'), nullable=False)
|
||||
investigator_uids = db.Column(db.ARRAY(db.String), nullable=True)
|
||||
inactive = db.Column(db.Boolean, default=False)
|
||||
requirements = db.Column(db.ARRAY(db.Integer), nullable=True)
|
||||
|
||||
|
||||
class StudyModelSchema(ModelSchema):
|
||||
class Meta:
|
||||
model = StudyModel
|
||||
include_fk = True # Includes foreign keys
|
||||
|
||||
protocol_builder_status = EnumField(ProtocolBuilderStatus)
|
||||
|
||||
|
||||
|
@ -9,7 +9,7 @@ from crc.models.workflow import WorkflowSpecModel
|
||||
from docxtpl import DocxTemplate
|
||||
import jinja2
|
||||
|
||||
from crc.services.FileService import FileService
|
||||
from crc.services.file_service import FileService
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
|
||||
|
||||
@ -19,7 +19,6 @@ class CompleteTemplate(object):
|
||||
as much feedback as possible. Some of this might move up to a higher level object or be
|
||||
passed into all tasks as we complete more work."""
|
||||
|
||||
|
||||
def do_task(self, task, *args, **kwargs):
|
||||
"""Entry point, mostly worried about wiring it all up."""
|
||||
if len(args) != 1:
|
||||
@ -38,7 +37,6 @@ class CompleteTemplate(object):
|
||||
.filter(FileModel.name == file_name) \
|
||||
.filter(FileModel.workflow_spec_id == workflow_spec_model.id).first()
|
||||
|
||||
|
||||
if file_data_model is None:
|
||||
raise ApiError(code="file_missing",
|
||||
message="Can not find a file called '%s' "
|
||||
|
@ -11,7 +11,6 @@ class FileService(object):
|
||||
|
||||
DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||
|
||||
|
||||
@staticmethod
|
||||
def add_workflow_spec_file(workflow_spec_id, name, content_type, binary_data):
|
||||
"""Create a new file and associate it with a workflow spec."""
|
@ -1,41 +1,52 @@
|
||||
import json
|
||||
from typing import List, Optional
|
||||
|
||||
import requests
|
||||
|
||||
from crc import app
|
||||
|
||||
STUDY_URL = app.config['PB_USER_STUDIES_URL']
|
||||
INVESTIGATOR_URL = app.config['PB_INVESTIGATORS_URL']
|
||||
REQUIRED_DOCS_URL = app.config['PB_REQUIRED_DOCS_URL']
|
||||
STUDY_DETAILS_URL = app.config['PB_STUDY_DETAILS_URL']
|
||||
from crc.models.protocol_builder import ProtocolBuilderStudy, ProtocolBuilderStudySchema, ProtocolBuilderInvestigator, \
|
||||
ProtocolBuilderRequiredDocument, ProtocolBuilderStudyDetails, ProtocolBuilderInvestigatorSchema, \
|
||||
ProtocolBuilderRequiredDocumentSchema, ProtocolBuilderStudyDetailsSchema
|
||||
|
||||
|
||||
class ProtocolBuilderService(object):
|
||||
STUDY_URL = app.config['PB_USER_STUDIES_URL']
|
||||
INVESTIGATOR_URL = app.config['PB_INVESTIGATORS_URL']
|
||||
REQUIRED_DOCS_URL = app.config['PB_REQUIRED_DOCS_URL']
|
||||
STUDY_DETAILS_URL = app.config['PB_STUDY_DETAILS_URL']
|
||||
|
||||
def get_studies(user_id):
|
||||
response = requests.get(STUDY_URL % user_id)
|
||||
if response.ok:
|
||||
return response
|
||||
else:
|
||||
return None
|
||||
@staticmethod
|
||||
def get_studies(user_id) -> Optional[List[ProtocolBuilderStudy]]:
|
||||
response = requests.get(ProtocolBuilderService.STUDY_URL % user_id)
|
||||
if response.ok and response.text:
|
||||
pb_studies = ProtocolBuilderStudySchema(many=True).loads(response.text)
|
||||
return pb_studies
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_investigators(study_id) -> Optional[List[ProtocolBuilderInvestigator]]:
|
||||
response = requests.get(ProtocolBuilderService.INVESTIGATOR_URL % study_id)
|
||||
if response.ok and response.text:
|
||||
pb_studies = ProtocolBuilderInvestigatorSchema(many=True).loads(response.text)
|
||||
return pb_studies
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_investigators(study_id):
|
||||
response = requests.get(INVESTIGATOR_URL % study_id)
|
||||
if response.ok:
|
||||
return response
|
||||
else:
|
||||
return None
|
||||
@staticmethod
|
||||
def get_required_docs(study_id) -> Optional[List[ProtocolBuilderRequiredDocument]]:
|
||||
response = requests.get(ProtocolBuilderService.REQUIRED_DOCS_URL % study_id)
|
||||
if response.ok and response.text:
|
||||
pb_studies = ProtocolBuilderRequiredDocumentSchema(many=True).loads(response.text)
|
||||
return pb_studies
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_required_docs(study_id):
|
||||
response = requests.get(REQUIRED_DOCS_URL % study_id)
|
||||
if response.ok:
|
||||
return response
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_study_details(study_id):
|
||||
response = requests.get(STUDY_DETAILS_URL % study_id)
|
||||
if response.ok:
|
||||
return response
|
||||
else:
|
||||
return None
|
||||
@staticmethod
|
||||
def get_study_details(study_id) -> Optional[ProtocolBuilderStudyDetails]:
|
||||
response = requests.get(ProtocolBuilderService.STUDY_DETAILS_URL % study_id)
|
||||
if response.ok and response.text:
|
||||
pb_study_details = ProtocolBuilderStudyDetailsSchema().loads(response.text)
|
||||
return pb_study_details
|
||||
else:
|
||||
return None
|
||||
|
@ -54,7 +54,6 @@ class CustomBpmnScriptEngine(BpmnScriptEngine):
|
||||
'The expression you provided does not exist:' + expression)
|
||||
|
||||
|
||||
|
||||
class MyCustomParser(BpmnDmnParser):
|
||||
"""
|
||||
A BPMN and DMN parser that can also parse Camunda forms.
|
||||
@ -62,6 +61,7 @@ class MyCustomParser(BpmnDmnParser):
|
||||
OVERRIDE_PARSER_CLASSES = BpmnDmnParser.OVERRIDE_PARSER_CLASSES
|
||||
OVERRIDE_PARSER_CLASSES.update(CamundaParser.OVERRIDE_PARSER_CLASSES)
|
||||
|
||||
|
||||
class WorkflowProcessor(object):
|
||||
_script_engine = CustomBpmnScriptEngine()
|
||||
_serializer = BpmnSerializer()
|
||||
@ -171,9 +171,6 @@ class WorkflowProcessor(object):
|
||||
next_task = task
|
||||
return next_task
|
||||
|
||||
|
||||
|
||||
|
||||
def complete_task(self, task):
|
||||
self.bpmn_workflow.complete_task_from_id(task.id)
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||
docker push sartography/cr-connect-workflow
|
||||
docker push sartography/cr-connect-workflow || true
|
||||
|
@ -1,16 +1,31 @@
|
||||
import datetime
|
||||
import glob
|
||||
import os
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
|
||||
from crc import app, db, session
|
||||
from crc.models.file import FileType, FileModel, FileDataModel
|
||||
from crc.models.study import StudyModel
|
||||
from crc.models.user import UserModel
|
||||
from crc.models.workflow import WorkflowSpecModel
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
|
||||
|
||||
class ExampleDataLoader:
|
||||
def make_data(self):
|
||||
users = [
|
||||
UserModel(
|
||||
uid='dhf8r',
|
||||
email_address='dhf8r@virginia.EDU',
|
||||
display_name='Daniel Harold Funk',
|
||||
affiliation='staff@virginia.edu;member@virginia.edu',
|
||||
eppn='dhf8r@virginia.edu',
|
||||
first_name='Daniel',
|
||||
last_name='Funk',
|
||||
title='SOFTWARE ENGINEER V'
|
||||
)
|
||||
]
|
||||
|
||||
studies = [
|
||||
StudyModel(
|
||||
id=1,
|
||||
@ -19,7 +34,8 @@ class ExampleDataLoader:
|
||||
protocol_builder_status='in_process',
|
||||
primary_investigator_id='dhf8r',
|
||||
sponsor='Sartography Pharmaceuticals',
|
||||
ind_number='1234'
|
||||
ind_number='1234',
|
||||
user_uid='dhf8r'
|
||||
),
|
||||
StudyModel(
|
||||
id=2,
|
||||
@ -28,7 +44,8 @@ class ExampleDataLoader:
|
||||
protocol_builder_status='in_process',
|
||||
primary_investigator_id='dhf8r',
|
||||
sponsor='Makerspace & Co.',
|
||||
ind_number='5678'
|
||||
ind_number='5678',
|
||||
user_uid='dhf8r'
|
||||
),
|
||||
]
|
||||
|
||||
@ -69,11 +86,11 @@ class ExampleDataLoader:
|
||||
description='How to take different paths based on input.')
|
||||
workflow_specifications += \
|
||||
self.create_spec(id="docx",
|
||||
name="docx",
|
||||
display_name="Form with document generation",
|
||||
description='the name says it all')
|
||||
name="docx",
|
||||
display_name="Form with document generation",
|
||||
description='the name says it all')
|
||||
|
||||
all_data = studies + workflow_specifications
|
||||
all_data = users + studies + workflow_specifications
|
||||
return all_data
|
||||
|
||||
def create_spec(self, id, name, display_name="", description="", filepath=None):
|
||||
@ -112,7 +129,7 @@ class ExampleDataLoader:
|
||||
try:
|
||||
file = open(file_path, "rb")
|
||||
data = file.read()
|
||||
if(is_primary):
|
||||
if (is_primary):
|
||||
bpmn: ElementTree.Element = ElementTree.fromstring(data)
|
||||
spec.primary_process_id = WorkflowProcessor.get_process_id(bpmn)
|
||||
print("Locating Process Id for " + filename + " " + spec.primary_process_id)
|
||||
@ -121,7 +138,6 @@ class ExampleDataLoader:
|
||||
file.close()
|
||||
return models
|
||||
|
||||
|
||||
@staticmethod
|
||||
def clean_db():
|
||||
session.flush() # Clear out any transactions before deleting it all to avoid spurious errors.
|
||||
|
@ -1,36 +0,0 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 0a6e0b829398
|
||||
Revises: ad5483cb7f3b
|
||||
Create Date: 2020-02-20 15:42:16.473470
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '0a6e0b829398'
|
||||
down_revision = 'ad5483cb7f3b'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('user', sa.Column('affiliation', sa.String(), nullable=True))
|
||||
op.add_column('user', sa.Column('eppn', sa.String(), nullable=True))
|
||||
op.add_column('user', sa.Column('first_name', sa.String(), nullable=True))
|
||||
op.add_column('user', sa.Column('last_name', sa.String(), nullable=True))
|
||||
op.add_column('user', sa.Column('title', sa.String(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('user', 'title')
|
||||
op.drop_column('user', 'last_name')
|
||||
op.drop_column('user', 'first_name')
|
||||
op.drop_column('user', 'eppn')
|
||||
op.drop_column('user', 'affiliation')
|
||||
# ### end Alembic commands ###
|
@ -1,35 +0,0 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: ad5483cb7f3b
|
||||
Revises: 02fcf09d9085
|
||||
Create Date: 2020-02-19 11:59:09.948767
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'ad5483cb7f3b'
|
||||
down_revision = '02fcf09d9085'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('user',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('uid', sa.String(), nullable=True),
|
||||
sa.Column('email_address', sa.String(), nullable=True),
|
||||
sa.Column('display_name', sa.String(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('uid')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('user')
|
||||
# ### end Alembic commands ###
|
@ -1,8 +1,8 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 02fcf09d9085
|
||||
Revises:
|
||||
Create Date: 2020-02-05 17:18:35.324675
|
||||
Revision ID: cb3a03c10a0e
|
||||
Revises:
|
||||
Create Date: 2020-02-28 11:12:56.150837
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '02fcf09d9085'
|
||||
revision = 'cb3a03c10a0e'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
@ -18,15 +18,18 @@ depends_on = None
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('study',
|
||||
op.create_table('user',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('uid', sa.String(), nullable=True),
|
||||
sa.Column('email_address', sa.String(), nullable=True),
|
||||
sa.Column('display_name', sa.String(), nullable=True),
|
||||
sa.Column('affiliation', sa.String(), nullable=True),
|
||||
sa.Column('eppn', sa.String(), nullable=True),
|
||||
sa.Column('first_name', sa.String(), nullable=True),
|
||||
sa.Column('last_name', sa.String(), nullable=True),
|
||||
sa.Column('title', sa.String(), nullable=True),
|
||||
sa.Column('last_updated', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('protocol_builder_status', sa.Enum('out_of_date', 'in_process', 'complete', 'updating', name='protocolbuilderstatus'), nullable=True),
|
||||
sa.Column('primary_investigator_id', sa.String(), nullable=True),
|
||||
sa.Column('sponsor', sa.String(), nullable=True),
|
||||
sa.Column('ind_number', sa.String(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('uid')
|
||||
)
|
||||
op.create_table('workflow_spec',
|
||||
sa.Column('id', sa.String(), nullable=False),
|
||||
@ -36,6 +39,22 @@ def upgrade():
|
||||
sa.Column('primary_process_id', sa.String(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('study',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('title', sa.String(), nullable=True),
|
||||
sa.Column('last_updated', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('protocol_builder_status', sa.Enum('out_of_date', 'in_process', 'complete', 'updating', name='protocolbuilderstatus'), nullable=True),
|
||||
sa.Column('primary_investigator_id', sa.String(), nullable=True),
|
||||
sa.Column('sponsor', sa.String(), nullable=True),
|
||||
sa.Column('hsr_number', sa.String(), nullable=True),
|
||||
sa.Column('ind_number', sa.String(), nullable=True),
|
||||
sa.Column('user_uid', sa.String(), nullable=False),
|
||||
sa.Column('investigator_uids', sa.ARRAY(sa.String()), nullable=True),
|
||||
sa.Column('inactive', sa.Boolean(), nullable=True),
|
||||
sa.Column('requirements', sa.ARRAY(sa.Integer()), nullable=True),
|
||||
sa.ForeignKeyConstraint(['user_uid'], ['user.uid'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('workflow',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('bpmn_workflow_json', sa.JSON(), nullable=True),
|
||||
@ -79,6 +98,7 @@ def downgrade():
|
||||
op.drop_table('file_data')
|
||||
op.drop_table('file')
|
||||
op.drop_table('workflow')
|
||||
op.drop_table('workflow_spec')
|
||||
op.drop_table('study')
|
||||
op.drop_table('workflow_spec')
|
||||
op.drop_table('user')
|
||||
# ### end Alembic commands ###
|
@ -3,11 +3,13 @@
|
||||
import json
|
||||
import os
|
||||
import unittest
|
||||
import urllib.parse
|
||||
|
||||
os.environ["TESTING"] = "true"
|
||||
|
||||
from crc.models.file import FileModel, FileDataModel
|
||||
from crc.models.workflow import WorkflowSpecModel
|
||||
|
||||
os.environ["TESTING"] = "true"
|
||||
from crc.models.user import UserModel
|
||||
|
||||
from crc import app, db, session
|
||||
from example_data import ExampleDataLoader
|
||||
@ -18,14 +20,13 @@ from example_data import ExampleDataLoader
|
||||
# logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Great class to inherit from, as it sets up and tears down
|
||||
# classes efficiently when we have a database in place.
|
||||
class BaseTest(unittest.TestCase):
|
||||
""" Great class to inherit from, as it sets up and tears down classes
|
||||
efficiently when we have a database in place.
|
||||
"""
|
||||
|
||||
auths = {}
|
||||
test_uid = "dhf8r"
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
@ -48,6 +49,25 @@ class BaseTest(unittest.TestCase):
|
||||
self.ctx.pop()
|
||||
self.auths = {}
|
||||
|
||||
def logged_in_headers(self, user=None, redirect_url='http://some/frontend/url'):
|
||||
if user is None:
|
||||
uid = self.test_uid
|
||||
user_info = {'uid': self.test_uid, 'first_name': 'Daniel', 'last_name': 'Funk',
|
||||
'email_address': 'dhf8r@virginia.edu'}
|
||||
else:
|
||||
uid = user.uid
|
||||
user_info = {'uid': user.uid, 'first_name': user.first_name, 'last_name': user.last_name,
|
||||
'email_address': user.email_address}
|
||||
|
||||
query_string = self.user_info_to_query_string(user_info, redirect_url)
|
||||
rv = self.app.get("/v1.0/sso_backdoor%s" % query_string, follow_redirects=False)
|
||||
self.assertTrue(rv.status_code == 302)
|
||||
self.assertTrue(str.startswith(rv.location, redirect_url))
|
||||
|
||||
user_model = session.query(UserModel).filter_by(uid=uid).first()
|
||||
self.assertIsNotNone(user_model.display_name)
|
||||
return dict(Authorization='Bearer ' + user_model.encode_auth_token().decode())
|
||||
|
||||
def load_example_data(self):
|
||||
from example_data import ExampleDataLoader
|
||||
ExampleDataLoader.clean_db()
|
||||
@ -66,7 +86,8 @@ class BaseTest(unittest.TestCase):
|
||||
self.assertIsNotNone(file_data)
|
||||
self.assertGreater(len(file_data), 0)
|
||||
|
||||
def load_test_spec(self, dir_name):
|
||||
@staticmethod
|
||||
def load_test_spec(dir_name):
|
||||
"""Loads a spec into the database based on a directory in /tests/data"""
|
||||
if session.query(WorkflowSpecModel).filter_by(id=dir_name).count() > 0:
|
||||
return
|
||||
@ -81,7 +102,8 @@ class BaseTest(unittest.TestCase):
|
||||
session.flush()
|
||||
return spec
|
||||
|
||||
def protocol_builder_response(self, file_name):
|
||||
@staticmethod
|
||||
def protocol_builder_response(file_name):
|
||||
filepath = os.path.join(app.root_path, '..', 'tests', 'data', 'pb_responses', file_name)
|
||||
with open(filepath, 'r') as myfile:
|
||||
data = myfile.read()
|
||||
@ -102,3 +124,16 @@ class BaseTest(unittest.TestCase):
|
||||
"Incorrect Valid Response:" + rv.status + ".")
|
||||
if code != 0:
|
||||
self.assertEqual(code, rv.status_code)
|
||||
|
||||
@staticmethod
|
||||
def user_info_to_query_string(user_info, redirect_url):
|
||||
query_string_list = []
|
||||
items = user_info.items()
|
||||
for key, value in items:
|
||||
query_string_list.append('%s=%s' % (key, urllib.parse.quote(value)))
|
||||
|
||||
query_string_list.append('redirect_url=%s' % redirect_url)
|
||||
|
||||
return '?%s' % '&'.join(query_string_list)
|
||||
|
||||
|
||||
|
@ -1,31 +1,9 @@
|
||||
import urllib.parse
|
||||
|
||||
from crc import db
|
||||
from crc.models.user import UserModel
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
|
||||
class TestAuthentication(BaseTest):
|
||||
test_uid = "dhf8r"
|
||||
|
||||
def logged_in_headers(self, user=None, redirect_url='http://some/frontend/url'):
|
||||
if user is None:
|
||||
uid = self.test_uid
|
||||
user_info = {'uid': self.test_uid, 'first_name': 'Daniel', 'last_name': 'Funk',
|
||||
'email_address': 'dhf8r@virginia.edu'}
|
||||
else:
|
||||
uid = user.uid
|
||||
user_info = {'uid': user.uid, 'first_name': user.first_name, 'last_name': user.last_name,
|
||||
'email_address': user.email_address}
|
||||
|
||||
query_string = self.user_info_to_query_string(user_info, redirect_url)
|
||||
rv = self.app.get("/v1.0/sso_backdoor%s" % query_string, follow_redirects=False)
|
||||
self.assertTrue(rv.status_code == 302)
|
||||
self.assertTrue(str.startswith(rv.location, redirect_url))
|
||||
|
||||
user_model = UserModel.query.filter_by(uid=uid).first()
|
||||
self.assertIsNotNone(user_model.display_name)
|
||||
return dict(Authorization='Bearer ' + user_model.encode_auth_token().decode())
|
||||
|
||||
def test_auth_token(self):
|
||||
self.load_example_data()
|
||||
@ -35,12 +13,13 @@ class TestAuthentication(BaseTest):
|
||||
self.assertEqual("dhf8r", user.decode_auth_token(auth_token))
|
||||
|
||||
def test_auth_creates_user(self):
|
||||
new_uid = 'czn1z';
|
||||
self.load_example_data()
|
||||
user = db.session.query(UserModel).filter(UserModel.uid == self.test_uid).first()
|
||||
user = db.session.query(UserModel).filter(UserModel.uid == new_uid).first()
|
||||
self.assertIsNone(user)
|
||||
|
||||
user_info = {'uid': self.test_uid, 'first_name': 'Daniel', 'last_name': 'Funk',
|
||||
'email_address': 'dhf8r@virginia.edu'}
|
||||
user_info = {'uid': new_uid, 'first_name': 'Cordi', 'last_name': 'Nator',
|
||||
'email_address': 'czn1z@virginia.edu'}
|
||||
redirect_url = 'http://worlds.best.website/admin'
|
||||
query_string = self.user_info_to_query_string(user_info, redirect_url)
|
||||
url = '/v1.0/sso_backdoor%s' % query_string
|
||||
@ -48,7 +27,7 @@ class TestAuthentication(BaseTest):
|
||||
self.assertTrue(rv_1.status_code == 302)
|
||||
self.assertTrue(str.startswith(rv_1.location, redirect_url))
|
||||
|
||||
user = db.session.query(UserModel).filter(UserModel.uid == self.test_uid).first()
|
||||
user = db.session.query(UserModel).filter(UserModel.uid == new_uid).first()
|
||||
self.assertIsNotNone(user)
|
||||
self.assertIsNotNone(user.display_name)
|
||||
self.assertIsNotNone(user.email_address)
|
||||
@ -69,15 +48,3 @@ class TestAuthentication(BaseTest):
|
||||
user = UserModel(uid="ajl2j", first_name='Aaron', last_name='Louie', email_address='ajl2j@virginia.edu')
|
||||
rv = self.app.get('/v1.0/user', headers=self.logged_in_headers(user, redirect_url='http://omg.edu/lolwut'))
|
||||
self.assert_success(rv)
|
||||
|
||||
def user_info_to_query_string(self, user_info, redirect_url):
|
||||
query_string_list = []
|
||||
items = user_info.items()
|
||||
for key, value in items:
|
||||
query_string_list.append('%s=%s' % (key, urllib.parse.quote(value)))
|
||||
|
||||
query_string_list.append('redirect_url=%s' % redirect_url)
|
||||
|
||||
return '?%s' % '&'.join(query_string_list)
|
||||
|
||||
|
||||
|
@ -1,40 +1,37 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from crc import db
|
||||
from crc.models.user import UserModel
|
||||
from crc.services import protocol_builder
|
||||
from crc.services.protocol_builder import ProtocolBuilderService
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
|
||||
class TestProtocolBuilder(BaseTest):
|
||||
|
||||
test_uid = "dhf8r"
|
||||
test_study_id = 1
|
||||
|
||||
@patch('crc.services.protocol_builder.requests.get')
|
||||
def test_get_studies(self, mock_get):
|
||||
mock_get.return_value.ok = True
|
||||
mock_get.return_value.json.return_value = self.protocol_builder_response('study_details.json')
|
||||
response = protocol_builder.get_studies(self.test_uid)
|
||||
mock_get.return_value.text = self.protocol_builder_response('user_studies.json')
|
||||
response = ProtocolBuilderService.get_studies(self.test_uid)
|
||||
self.assertIsNotNone(response)
|
||||
|
||||
@patch('crc.services.protocol_builder.requests.get')
|
||||
def test_get_investigators(self, mock_get):
|
||||
mock_get.return_value.ok = True
|
||||
mock_get.return_value.json.return_value = self.protocol_builder_response('investigators.json')
|
||||
response = protocol_builder.get_studies(self.test_study_id)
|
||||
self.assertIsNotNone(response)
|
||||
|
||||
@patch('crc.services.protocol_builder.requests.get')
|
||||
def test_get_details(self, mock_get):
|
||||
mock_get.return_value.ok = True
|
||||
mock_get.return_value.json.return_value = self.protocol_builder_response('study_details.json')
|
||||
response = protocol_builder.get_studies(self.test_study_id)
|
||||
mock_get.return_value.text = self.protocol_builder_response('investigators.json')
|
||||
response = ProtocolBuilderService.get_investigators(self.test_study_id)
|
||||
self.assertIsNotNone(response)
|
||||
|
||||
@patch('crc.services.protocol_builder.requests.get')
|
||||
def test_get_required_docs(self, mock_get):
|
||||
mock_get.return_value.ok = True
|
||||
mock_get.return_value.json.return_value = self.protocol_builder_response('required_docs.json')
|
||||
response = protocol_builder.get_studies(self.test_study_id)
|
||||
self.assertIsNotNone(response)
|
||||
mock_get.return_value.text = self.protocol_builder_response('required_docs.json')
|
||||
response = ProtocolBuilderService.get_required_docs(self.test_study_id)
|
||||
self.assertIsNotNone(response)
|
||||
|
||||
@patch('crc.services.protocol_builder.requests.get')
|
||||
def test_get_details(self, mock_get):
|
||||
mock_get.return_value.ok = True
|
||||
mock_get.return_value.text = self.protocol_builder_response('study_details.json')
|
||||
response = ProtocolBuilderService.get_study_details(self.test_study_id)
|
||||
self.assertIsNotNone(response)
|
||||
|
@ -1,9 +1,9 @@
|
||||
import json
|
||||
from datetime import datetime, tzinfo, timezone
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from crc import session
|
||||
from crc.models.file import FileModel
|
||||
from crc.models.study import StudyModel, StudyModelSchema, ProtocolBuilderStatus
|
||||
from crc.models.study import StudyModel, StudyModelSchema
|
||||
from crc.models.protocol_builder import ProtocolBuilderStatus
|
||||
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel, WorkflowStatus, \
|
||||
WorkflowApiSchema
|
||||
from tests.base_test import BaseTest
|
||||
@ -20,15 +20,18 @@ class TestStudyApi(BaseTest):
|
||||
self.load_example_data()
|
||||
study = {
|
||||
"id": 12345,
|
||||
"title": "Phase III Trial of Genuine People Personalities (GPP) Autonomous Intelligent Emotional Agents for Interstellar Spacecraft",
|
||||
"title": "Phase III Trial of Genuine People Personalities (GPP) Autonomous Intelligent Emotional Agents "
|
||||
"for Interstellar Spacecraft",
|
||||
"last_updated": datetime.now(tz=timezone.utc),
|
||||
"protocol_builder_status": ProtocolBuilderStatus.in_process,
|
||||
"primary_investigator_id": "tricia.marie.mcmillan@heartofgold.edu",
|
||||
"sponsor": "Sirius Cybernetics Corporation",
|
||||
"ind_number": "567890",
|
||||
"user_uid": "dhf8r",
|
||||
}
|
||||
rv = self.app.post('/v1.0/study',
|
||||
content_type="application/json",
|
||||
headers=self.logged_in_headers(),
|
||||
data=json.dumps(StudyModelSchema().dump(study)))
|
||||
self.assert_success(rv)
|
||||
db_study = session.query(StudyModel).filter_by(id=12345).first()
|
||||
@ -39,8 +42,7 @@ class TestStudyApi(BaseTest):
|
||||
self.assertEqual(study["primary_investigator_id"], db_study.primary_investigator_id)
|
||||
self.assertEqual(study["sponsor"], db_study.sponsor)
|
||||
self.assertEqual(study["ind_number"], db_study.ind_number)
|
||||
|
||||
|
||||
self.assertEqual(study["user_uid"], db_study.user_uid)
|
||||
|
||||
def test_update_study(self):
|
||||
self.load_example_data()
|
||||
@ -48,8 +50,9 @@ class TestStudyApi(BaseTest):
|
||||
study.title = "Pilot Study of Fjord Placement for Single Fraction Outcomes to Cortisol Susceptibility"
|
||||
study.protocol_builder_status = ProtocolBuilderStatus.complete
|
||||
rv = self.app.put('/v1.0/study/%i' % study.id,
|
||||
content_type="application/json",
|
||||
data=json.dumps(StudyModelSchema().dump(study)))
|
||||
content_type="application/json",
|
||||
headers=self.logged_in_headers(),
|
||||
data=json.dumps(StudyModelSchema().dump(study)))
|
||||
self.assert_success(rv)
|
||||
db_study = session.query(StudyModel).filter_by(id=study.id).first()
|
||||
self.assertIsNotNone(db_study)
|
||||
@ -61,6 +64,7 @@ class TestStudyApi(BaseTest):
|
||||
study = session.query(StudyModel).first()
|
||||
rv = self.app.get('/v1.0/study/%i' % study.id,
|
||||
follow_redirects=True,
|
||||
headers=self.logged_in_headers(),
|
||||
content_type="application/json")
|
||||
self.assert_success(rv)
|
||||
json_data = json.loads(rv.get_data(as_text=True))
|
||||
@ -79,7 +83,9 @@ class TestStudyApi(BaseTest):
|
||||
study = session.query(StudyModel).first()
|
||||
self.assertEqual(0, session.query(WorkflowModel).count())
|
||||
spec = session.query(WorkflowSpecModel).first()
|
||||
rv = self.app.post('/v1.0/study/%i/workflows' % study.id, content_type="application/json",
|
||||
rv = self.app.post('/v1.0/study/%i/workflows' % study.id,
|
||||
content_type="application/json",
|
||||
headers=self.logged_in_headers(),
|
||||
data=json.dumps(WorkflowSpecModelSchema().dump(spec)))
|
||||
self.assert_success(rv)
|
||||
self.assertEqual(1, session.query(WorkflowModel).count())
|
||||
@ -97,7 +103,9 @@ class TestStudyApi(BaseTest):
|
||||
self.load_example_data()
|
||||
study = session.query(StudyModel).first()
|
||||
spec = session.query(WorkflowSpecModel).first()
|
||||
rv = self.app.post('/v1.0/study/%i/workflows' % study.id, content_type="application/json",
|
||||
rv = self.app.post('/v1.0/study/%i/workflows' % study.id,
|
||||
content_type="application/json",
|
||||
headers=self.logged_in_headers(),
|
||||
data=json.dumps(WorkflowSpecModelSchema().dump(spec)))
|
||||
self.assertEqual(1, session.query(WorkflowModel).count())
|
||||
json_data = json.loads(rv.get_data(as_text=True))
|
||||
|
@ -13,8 +13,12 @@ class TestTasksApi(BaseTest):
|
||||
def create_workflow(self, workflow_name):
|
||||
study = session.query(StudyModel).first()
|
||||
spec = session.query(WorkflowSpecModel).filter_by(id=workflow_name).first()
|
||||
self.app.post('/v1.0/study/%i/workflows' % study.id, content_type="application/json",
|
||||
data=json.dumps(WorkflowSpecModelSchema().dump(spec)))
|
||||
rv = self.app.post(
|
||||
'/v1.0/study/%i/workflows' % study.id,
|
||||
headers=self.logged_in_headers(),
|
||||
content_type="application/json",
|
||||
data=json.dumps(WorkflowSpecModelSchema().dump(spec)))
|
||||
self.assert_success(rv)
|
||||
workflow = session.query(WorkflowModel).filter_by(study_id=study.id, workflow_spec_id=workflow_name).first()
|
||||
return workflow
|
||||
|
||||
@ -183,4 +187,4 @@ class TestTasksApi(BaseTest):
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
self.assertEqual("EndEvent_0u1cgrf", workflow_api.next_task['name'])
|
||||
self.assertIsNotNone(workflow_api.next_task['documentation'])
|
||||
self.assertTrue("norris" in workflow_api.next_task['documentation'])
|
||||
self.assertTrue("norris" in workflow_api.next_task['documentation'])
|
||||
|
@ -1,11 +1,8 @@
|
||||
import json
|
||||
from datetime import datetime, tzinfo, timezone
|
||||
|
||||
from crc import session
|
||||
from crc.models.file import FileModel
|
||||
from crc.models.study import StudyModel, StudyModelSchema, ProtocolBuilderStatus
|
||||
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel, WorkflowStatus, \
|
||||
WorkflowApiSchema
|
||||
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user