Merge pull request #14 from sartography/feature/protocol-builder

Feature/protocol builder
This commit is contained in:
Aaron Louie 2020-02-28 11:58:53 -05:00 committed by GitHub
commit cd07d9d95e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 576 additions and 260 deletions

View File

@ -33,6 +33,7 @@ recommonmark = "*"
psycopg2-binary = "*"
docxtpl = "*"
flask-sso = "*"
python-dateutil = "*"
[requires]
python_version = "3.7"

35
Pipfile.lock generated
View File

@ -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": [

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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

View 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

View File

@ -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)

View File

@ -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' "

View File

@ -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."""

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -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 ###

View File

@ -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 ###

View File

@ -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 ###

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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))

View File

@ -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'])

View File

@ -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