Merge branch 'rrt/dev' into dev
This commit is contained in:
commit
00955723b0
|
@ -28,6 +28,7 @@ FRONTEND_AUTH_CALLBACK = environ.get('FRONTEND_AUTH_CALLBACK', default="http://l
|
|||
SWAGGER_AUTH_KEY = environ.get('SWAGGER_AUTH_KEY', default="SWAGGER")
|
||||
|
||||
# %s/%i placeholders expected for uva_id and study_id in various calls.
|
||||
PB_ENABLED = environ.get('PB_ENABLED', default=True)
|
||||
PB_BASE_URL = environ.get('PB_BASE_URL', default="http://localhost:5001/pb/")
|
||||
PB_USER_STUDIES_URL = environ.get('PB_USER_STUDIES_URL', default=PB_BASE_URL + "user_studies?uva_id=%s")
|
||||
PB_INVESTIGATORS_URL = environ.get('PB_INVESTIGATORS_URL', default=PB_BASE_URL + "investigators?studyid=%i")
|
||||
|
|
|
@ -6,6 +6,7 @@ DEVELOPMENT = True
|
|||
TESTING = True
|
||||
SQLALCHEMY_DATABASE_URI = "postgresql://crc_user:crc_pass@localhost:5432/crc_test"
|
||||
TOKEN_AUTH_SECRET_KEY = "Shhhh!!! This is secret! And better darn well not show up in prod."
|
||||
PB_ENABLED = False
|
||||
|
||||
print('### USING TESTING CONFIG: ###')
|
||||
print('SQLALCHEMY_DATABASE_URI = ', SQLALCHEMY_DATABASE_URI)
|
||||
|
|
100
crc/api.yml
100
crc/api.yml
|
@ -110,6 +110,19 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Study"
|
||||
/study-files:
|
||||
get:
|
||||
operationId: crc.api.study.all_studies_and_files
|
||||
summary: Provides a list of studies with submitted files
|
||||
tags:
|
||||
- Studies
|
||||
responses:
|
||||
'200':
|
||||
description: An array of studies, with submitted files, ordered by the last modified date.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Study"
|
||||
/study/{study_id}:
|
||||
parameters:
|
||||
- name: study_id
|
||||
|
@ -156,26 +169,6 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Study"
|
||||
/study-update/{study_id}:
|
||||
post:
|
||||
operationId: crc.api.study.post_update_study_from_protocol_builder
|
||||
summary: If the study is up-to-date with Protocol Builder, returns a 304 Not Modified. If out of date, return a 202 Accepted and study state changes to updating.
|
||||
tags:
|
||||
- Study Status
|
||||
parameters:
|
||||
- name: study_id
|
||||
in: path
|
||||
required: true
|
||||
description: The id of the study that should be checked for updates.
|
||||
schema:
|
||||
type: integer
|
||||
format: int32
|
||||
responses:
|
||||
'304':
|
||||
description: Study is currently up to date and does not need to be reloaded from Protocol Builder
|
||||
'202':
|
||||
description: Request accepted, will preform an update. Study state set to "updating"
|
||||
|
||||
/workflow-specification:
|
||||
get:
|
||||
operationId: crc.api.workflow.all_specifications
|
||||
|
@ -755,13 +748,6 @@ paths:
|
|||
schema:
|
||||
type: string
|
||||
/render_docx:
|
||||
parameters:
|
||||
- name: data
|
||||
in: query
|
||||
required: true
|
||||
description: The json data to use in populating the template
|
||||
schema:
|
||||
type: string
|
||||
put:
|
||||
operationId: crc.api.tools.render_docx
|
||||
security: [] # Disable security for this endpoint only.
|
||||
|
@ -777,6 +763,9 @@ paths:
|
|||
file:
|
||||
type: string
|
||||
format: binary
|
||||
data:
|
||||
type: string
|
||||
format: json
|
||||
responses:
|
||||
'200':
|
||||
description: Returns the generated document.
|
||||
|
@ -802,6 +791,54 @@ paths:
|
|||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Script"
|
||||
/approval:
|
||||
parameters:
|
||||
- name: approver_uid
|
||||
in: query
|
||||
required: false
|
||||
description: Restrict results to a given approver uid, maybe we restrict the use of this at somepoint.
|
||||
schema:
|
||||
type: string
|
||||
get:
|
||||
operationId: crc.api.approval.get_approvals
|
||||
summary: Provides a list of workflows approvals
|
||||
tags:
|
||||
- Approvals
|
||||
responses:
|
||||
'200':
|
||||
description: An array of approvals
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Approval"
|
||||
/approval/{approval_id}:
|
||||
parameters:
|
||||
- name: approval_id
|
||||
in: path
|
||||
required: true
|
||||
description: The id of the approval in question.
|
||||
schema:
|
||||
type: integer
|
||||
format: int32
|
||||
put:
|
||||
operationId: crc.api.approval.update_approval
|
||||
summary: Updates an approval with the given parameters
|
||||
tags:
|
||||
- Approvals
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Approval'
|
||||
responses:
|
||||
'200':
|
||||
description: Study updated successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Approval"
|
||||
components:
|
||||
securitySchemes:
|
||||
jwt:
|
||||
|
@ -1209,7 +1246,10 @@ components:
|
|||
readOnly: true
|
||||
task:
|
||||
$ref: "#/components/schemas/Task"
|
||||
|
||||
|
||||
|
||||
Approval:
|
||||
properties:
|
||||
id:
|
||||
type: number
|
||||
format: integer
|
||||
example: 5
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
from crc.models.approval import ApprovalModel, Approval
|
||||
|
||||
|
||||
def get_approvals(approver_uid = None):
|
||||
approval_model = ApprovalModel()
|
||||
approval = Approval.from_model(approval_model)
|
||||
return {}
|
||||
|
||||
def update_approval(approval_id):
|
||||
return {}
|
|
@ -1,20 +1,16 @@
|
|||
from typing import List
|
||||
|
||||
from connexion import NoContent
|
||||
from flask import g
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from crc import session
|
||||
from crc.api.common import ApiError, ApiErrorSchema
|
||||
from crc.models.protocol_builder import ProtocolBuilderStatus, ProtocolBuilderStudy
|
||||
from crc.models.study import StudySchema, StudyModel, Study
|
||||
from crc.models.study import StudySchema, StudyFilesSchema, StudyModel, Study
|
||||
from crc.services.protocol_builder import ProtocolBuilderService
|
||||
from crc.services.study_service import StudyService
|
||||
|
||||
|
||||
def add_study(body):
|
||||
"""This should never get called, and is subject to deprication. Studies
|
||||
should be added through the protocol builder only."""
|
||||
"""Or any study like object. """
|
||||
study: Study = StudySchema().load(body)
|
||||
study_model = StudyModel(**study.model_args())
|
||||
session.add(study_model)
|
||||
|
@ -59,30 +55,17 @@ def delete_study(study_id):
|
|||
|
||||
|
||||
def all_studies():
|
||||
"""Returns all the studies associated with the current user. Assures we are
|
||||
in sync with values read in from the protocol builder. """
|
||||
StudyService.synch_all_studies_with_protocol_builder(g.user)
|
||||
"""Returns all the studies associated with the current user. """
|
||||
StudyService.synch_with_protocol_builder_if_enabled(g.user)
|
||||
studies = StudyService.get_studies_for_user(g.user)
|
||||
results = StudySchema(many=True).dump(studies)
|
||||
return results
|
||||
|
||||
|
||||
def post_update_study_from_protocol_builder(study_id):
|
||||
"""Update a single study based on data received from
|
||||
the protocol builder."""
|
||||
|
||||
db_study = session.query(StudyModel).filter_by(study_id=study_id).all()
|
||||
pb_studies: List[ProtocolBuilderStudy] = ProtocolBuilderService.get_studies(g.user.uid)
|
||||
pb_study = next((pbs for pbs in pb_studies if pbs.STUDYID == study_id), None)
|
||||
if pb_study:
|
||||
db_study.update_from_protocol_builder(pb_study)
|
||||
else:
|
||||
db_study.inactive = True
|
||||
db_study.protocol_builder_status = ProtocolBuilderStatus.ABANDONED
|
||||
|
||||
return NoContent, 304
|
||||
|
||||
|
||||
|
||||
def all_studies_and_files():
|
||||
"""Returns all studies with submitted files"""
|
||||
studies = StudyService.get_studies_with_files()
|
||||
results = StudyFilesSchema(many=True).dump(studies)
|
||||
return results
|
||||
|
||||
|
||||
|
|
|
@ -25,13 +25,14 @@ def render_markdown(data, template):
|
|||
raise ApiError(code="invalid", message=str(e))
|
||||
|
||||
|
||||
def render_docx(data):
|
||||
def render_docx():
|
||||
"""
|
||||
Provides a quick way to verify that a Jinja docx template will work properly on a given json
|
||||
data structure. Useful for folks that are building these templates.
|
||||
"""
|
||||
try:
|
||||
file = connexion.request.files['file']
|
||||
data = connexion.request.form['data']
|
||||
target_stream = CompleteTemplate().make_template(file, json.loads(data))
|
||||
return send_file(
|
||||
io.BytesIO(target_stream.read()),
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
import enum
|
||||
|
||||
from marshmallow import INCLUDE
|
||||
|
||||
from crc import db, ma
|
||||
from crc.models.study import StudyModel
|
||||
from crc.models.workflow import WorkflowModel
|
||||
|
||||
|
||||
class ApprovalStatus(enum.Enum):
|
||||
WAITING = "WAITING" # no one has done jack.
|
||||
APPROVED = "APPROVED" # approved by the reviewer
|
||||
DECLINED = "DECLINED" # rejected by the reviewer
|
||||
CANCELED = "CANCELED" # The document was replaced with a new version and this review is no longer needed.
|
||||
|
||||
|
||||
class ApprovalModel(db.Model):
|
||||
__tablename__ = 'approval'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
study_id = db.Column(db.Integer, db.ForeignKey(StudyModel.id), nullable=False)
|
||||
study = db.relationship(StudyModel, backref='approval')
|
||||
workflow_id = db.Column(db.Integer, db.ForeignKey(WorkflowModel.id), nullable=False)
|
||||
workflow_version = db.Column(db.String)
|
||||
approver_uid = db.Column(db.String) # Not linked to user model, as they may not have logged in yet.
|
||||
status = db.Column(db.String)
|
||||
message = db.Column(db.String)
|
||||
|
||||
|
||||
class Approval(object):
|
||||
|
||||
@classmethod
|
||||
def from_model(cls, model: ApprovalModel):
|
||||
instance = cls()
|
||||
|
||||
instance.id = model.id
|
||||
instance.workflow_version = model.workflow_version
|
||||
instance.approver_uid = model.approver_uid
|
||||
instance.status = model.status
|
||||
instance.study_id = model.study_id
|
||||
if model.study:
|
||||
instance.title = model.study.title
|
||||
|
||||
|
||||
class ApprovalSchema(ma.Schema):
|
||||
class Meta:
|
||||
model = Approval
|
||||
fields = ["id", "workflow_version", "approver_uid", "status",
|
||||
"study_id", "title"]
|
||||
unknown = INCLUDE
|
||||
|
||||
# Carlos: Here is the data structure I was trying to imagine.
|
||||
# If I were to continue down my current traing of thought, I'd create
|
||||
# another class called just "Approval" that can take an ApprovalModel from the
|
||||
# database and construct a data structure like this one, that can
|
||||
# be provided to the API at an /approvals endpoint with GET and PUT
|
||||
# dat = { "approvals": [
|
||||
# {"id": 1,
|
||||
# "study_id": 20,
|
||||
# "workflow_id": 454,
|
||||
# "study_title": "Dan Funk (dhf8r)", # Really it's just the name of the Principal Investigator
|
||||
# "workflow_version": "21",
|
||||
# "approver": { # Pulled from ldap
|
||||
# "uid": "bgb22",
|
||||
# "display_name": "Billy Bob (bgb22)",
|
||||
# "title": "E42:He's a hoopy frood",
|
||||
# "department": "E0:EN-Eng Study of Parallel Universes",
|
||||
# },
|
||||
# "files": [
|
||||
# {
|
||||
# "id": 124,
|
||||
# "name": "ResearchRestart.docx",
|
||||
# "content_type": "docx-something-whatever"
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
# ...
|
||||
# ]
|
|
@ -6,7 +6,7 @@ from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
|
|||
from sqlalchemy import func, Index
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
from crc import db
|
||||
from crc import db, ma
|
||||
|
||||
|
||||
class FileType(enum.Enum):
|
||||
|
@ -139,3 +139,9 @@ class LookupDataSchema(SQLAlchemyAutoSchema):
|
|||
include_relationships = False
|
||||
include_fk = False # Includes foreign keys
|
||||
|
||||
|
||||
class SimpleFileSchema(ma.Schema):
|
||||
|
||||
class Meta:
|
||||
model = FileModel
|
||||
fields = ["name"]
|
||||
|
|
|
@ -5,6 +5,7 @@ from sqlalchemy import func
|
|||
|
||||
from crc import db, ma
|
||||
from crc.api.common import ApiErrorSchema
|
||||
from crc.models.file import FileModel, SimpleFileSchema
|
||||
from crc.models.protocol_builder import ProtocolBuilderStatus, ProtocolBuilderStudy
|
||||
from crc.models.workflow import WorkflowSpecCategoryModel, WorkflowState, WorkflowStatus, WorkflowSpecModel, \
|
||||
WorkflowModel
|
||||
|
@ -39,6 +40,10 @@ class StudyModel(db.Model):
|
|||
if self.on_hold:
|
||||
self.protocol_builder_status = ProtocolBuilderStatus.HOLD
|
||||
|
||||
def files(self):
|
||||
_files = FileModel.query.filter_by(workflow_id=self.workflow[0].id)
|
||||
return _files
|
||||
|
||||
|
||||
class WorkflowMetadata(object):
|
||||
def __init__(self, id, name, display_name, description, spec_version, category_id, state: WorkflowState, status: WorkflowStatus,
|
||||
|
@ -154,3 +159,16 @@ class StudySchema(ma.Schema):
|
|||
def make_study(self, data, **kwargs):
|
||||
"""Can load the basic study data for updates to the database, but categories are write only"""
|
||||
return Study(**data)
|
||||
|
||||
|
||||
class StudyFilesSchema(ma.Schema):
|
||||
|
||||
# files = fields.List(fields.Nested(SimpleFileSchema), dump_only=True)
|
||||
files = fields.Method('_files')
|
||||
|
||||
class Meta:
|
||||
model = Study
|
||||
additional = ["id", "title", "last_updated", "primary_investigator_id"]
|
||||
|
||||
def _files(self, obj):
|
||||
return [file.name for file in obj.files()]
|
||||
|
|
|
@ -19,6 +19,8 @@ class UserModel(db.Model):
|
|||
last_name = db.Column(db.String, nullable=True)
|
||||
title = db.Column(db.String, nullable=True)
|
||||
|
||||
# Add Department and School
|
||||
|
||||
|
||||
def encode_auth_token(self):
|
||||
"""
|
||||
|
|
|
@ -73,10 +73,12 @@ class WorkflowModel(db.Model):
|
|||
bpmn_workflow_json = db.Column(db.JSON)
|
||||
status = db.Column(db.Enum(WorkflowStatus))
|
||||
study_id = db.Column(db.Integer, db.ForeignKey('study.id'))
|
||||
study = db.relationship("StudyModel", backref='workflow')
|
||||
workflow_spec_id = db.Column(db.String, db.ForeignKey('workflow_spec.id'))
|
||||
workflow_spec = db.relationship("WorkflowSpecModel")
|
||||
spec_version = db.Column(db.String)
|
||||
total_tasks = db.Column(db.Integer, default=0)
|
||||
completed_tasks = db.Column(db.Integer, default=0)
|
||||
# task_history = db.Column(db.ARRAY(db.String), default=[]) # The history stack of user completed tasks.
|
||||
last_updated = db.Column(db.DateTime)
|
||||
last_updated = db.Column(db.DateTime)
|
||||
# todo: Add a version that represents the files associated with this workflow
|
||||
# version = "32"
|
|
@ -10,6 +10,7 @@ from crc.models.protocol_builder import ProtocolBuilderStudy, ProtocolBuilderStu
|
|||
|
||||
|
||||
class ProtocolBuilderService(object):
|
||||
ENABLED = app.config['PB_ENABLED']
|
||||
STUDY_URL = app.config['PB_USER_STUDIES_URL']
|
||||
INVESTIGATOR_URL = app.config['PB_INVESTIGATORS_URL']
|
||||
REQUIRED_DOCS_URL = app.config['PB_REQUIRED_DOCS_URL']
|
||||
|
@ -17,6 +18,7 @@ class ProtocolBuilderService(object):
|
|||
|
||||
@staticmethod
|
||||
def get_studies(user_id) -> {}:
|
||||
ProtocolBuilderService.__enabled_or_raise()
|
||||
if not isinstance(user_id, str):
|
||||
raise ApiError("invalid_user_id", "This user id is invalid: " + str(user_id))
|
||||
response = requests.get(ProtocolBuilderService.STUDY_URL % user_id)
|
||||
|
@ -30,40 +32,32 @@ class ProtocolBuilderService(object):
|
|||
|
||||
@staticmethod
|
||||
def get_investigators(study_id) -> {}:
|
||||
ProtocolBuilderService.check_args(study_id)
|
||||
response = requests.get(ProtocolBuilderService.INVESTIGATOR_URL % study_id)
|
||||
if response.ok and response.text:
|
||||
pb_studies = json.loads(response.text)
|
||||
return pb_studies
|
||||
else:
|
||||
raise ApiError("protocol_builder_error",
|
||||
"Received an invalid response from the protocol builder (status %s): %s" %
|
||||
(response.status_code, response.text))
|
||||
return ProtocolBuilderService.__make_request(study_id, ProtocolBuilderService.INVESTIGATOR_URL)
|
||||
|
||||
@staticmethod
|
||||
def get_required_docs(study_id) -> Optional[List[ProtocolBuilderRequiredDocument]]:
|
||||
ProtocolBuilderService.check_args(study_id)
|
||||
response = requests.get(ProtocolBuilderService.REQUIRED_DOCS_URL % study_id)
|
||||
return ProtocolBuilderService.__make_request(study_id, ProtocolBuilderService.REQUIRED_DOCS_URL)
|
||||
|
||||
@staticmethod
|
||||
def get_study_details(study_id) -> {}:
|
||||
return ProtocolBuilderService.__make_request(study_id, ProtocolBuilderService.STUDY_DETAILS_URL)
|
||||
|
||||
@staticmethod
|
||||
def __enabled_or_raise():
|
||||
if not ProtocolBuilderService.ENABLED:
|
||||
raise ApiError("protocol_builder_disabled", "The Protocol Builder Service is currently disabled.")
|
||||
|
||||
@staticmethod
|
||||
def __make_request(study_id, url):
|
||||
ProtocolBuilderService.__enabled_or_raise()
|
||||
if not isinstance(study_id, int):
|
||||
raise ApiError("invalid_study_id", "This study id is invalid: " + str(study_id))
|
||||
response = requests.get(url % study_id)
|
||||
if response.ok and response.text:
|
||||
return json.loads(response.text)
|
||||
else:
|
||||
raise ApiError("protocol_builder_error",
|
||||
"Received an invalid response from the protocol builder (status %s): %s" %
|
||||
(response.status_code, response.text))
|
||||
"Received an invalid response from the protocol builder (status %s): %s when calling "
|
||||
"url '%s'." %
|
||||
(response.status_code, response.text, url))
|
||||
|
||||
@staticmethod
|
||||
def get_study_details(study_id) -> {}:
|
||||
ProtocolBuilderService.check_args(study_id)
|
||||
response = requests.get(ProtocolBuilderService.STUDY_DETAILS_URL % study_id)
|
||||
if response.ok and response.text:
|
||||
pb_study_details = json.loads(response.text)
|
||||
return pb_study_details
|
||||
else:
|
||||
raise ApiError("protocol_builder_error",
|
||||
"Received an invalid response from the protocol builder (status %s): %s" %
|
||||
(response.status_code, response.text))
|
||||
|
||||
@staticmethod
|
||||
def check_args(study_id):
|
||||
if not isinstance(study_id, int):
|
||||
raise ApiError("invalid_study_id", "This study id is invalid: " + str(study_id))
|
||||
|
|
|
@ -32,6 +32,12 @@ class StudyService(object):
|
|||
studies.append(StudyService.get_study(study_model.id, study_model))
|
||||
return studies
|
||||
|
||||
@staticmethod
|
||||
def get_studies_with_files():
|
||||
"""Returns a list of all studies"""
|
||||
db_studies = session.query(StudyModel).all()
|
||||
return db_studies
|
||||
|
||||
@staticmethod
|
||||
def get_study(study_id, study_model: StudyModel = None):
|
||||
"""Returns a study model that contains all the workflows organized by category.
|
||||
|
@ -110,23 +116,29 @@ class StudyService(object):
|
|||
"""Returns a list of documents related to the study, and any file information
|
||||
that is available.."""
|
||||
|
||||
# Get PB required docs
|
||||
try:
|
||||
pb_docs = ProtocolBuilderService.get_required_docs(study_id=study_id)
|
||||
except requests.exceptions.ConnectionError as ce:
|
||||
app.logger.error("Failed to connect to the Protocol Builder - %s" % str(ce))
|
||||
# Get PB required docs, if Protocol Builder Service is enabled.
|
||||
if ProtocolBuilderService.ENABLED:
|
||||
try:
|
||||
pb_docs = ProtocolBuilderService.get_required_docs(study_id=study_id)
|
||||
except requests.exceptions.ConnectionError as ce:
|
||||
app.logger.error("Failed to connect to the Protocol Builder - %s" % str(ce))
|
||||
pb_docs = []
|
||||
else:
|
||||
pb_docs = []
|
||||
|
||||
# Loop through all known document types, get the counts for those files, and use pb_docs to mark those required.
|
||||
# Loop through all known document types, get the counts for those files,
|
||||
# and use pb_docs to mark those as required.
|
||||
doc_dictionary = FileService.get_reference_data(FileService.DOCUMENT_LIST, 'code', ['id'])
|
||||
|
||||
documents = {}
|
||||
for code, doc in doc_dictionary.items():
|
||||
|
||||
pb_data = next((item for item in pb_docs if int(item['AUXDOCID']) == int(doc['id'])), None)
|
||||
doc['required'] = False
|
||||
if pb_data:
|
||||
doc['required'] = True
|
||||
if ProtocolBuilderService.ENABLED:
|
||||
pb_data = next((item for item in pb_docs if int(item['AUXDOCID']) == int(doc['id'])), None)
|
||||
doc['required'] = False
|
||||
if pb_data:
|
||||
doc['required'] = True
|
||||
|
||||
doc['study_id'] = study_id
|
||||
doc['code'] = code
|
||||
|
||||
|
@ -153,7 +165,6 @@ class StudyService(object):
|
|||
doc['status'] = workflow.status.value
|
||||
|
||||
documents[code] = doc
|
||||
|
||||
return documents
|
||||
|
||||
|
||||
|
@ -201,9 +212,13 @@ class StudyService(object):
|
|||
|
||||
|
||||
@staticmethod
|
||||
def synch_all_studies_with_protocol_builder(user):
|
||||
def synch_with_protocol_builder_if_enabled(user):
|
||||
"""Assures that the studies we have locally for the given user are
|
||||
in sync with the studies available in protocol builder. """
|
||||
|
||||
if not ProtocolBuilderService.ENABLED:
|
||||
return
|
||||
|
||||
# Get studies matching this user from Protocol Builder
|
||||
pb_studies: List[ProtocolBuilderStudy] = ProtocolBuilderService.get_studies(user.uid)
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
# Set environment variable to testing before loading.
|
||||
# IMPORTANT - Environment must be loaded before app, models, etc....
|
||||
import json
|
||||
import os
|
||||
os.environ["TESTING"] = "true"
|
||||
|
||||
import json
|
||||
import unittest
|
||||
import urllib.parse
|
||||
import datetime
|
||||
|
@ -10,10 +12,6 @@ from crc.models.protocol_builder import ProtocolBuilderStatus
|
|||
from crc.models.study import StudyModel
|
||||
from crc.services.file_service import FileService
|
||||
from crc.services.study_service import StudyService
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
|
||||
os.environ["TESTING"] = "true"
|
||||
|
||||
from crc.models.file import FileModel, FileDataModel, CONTENT_TYPES
|
||||
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel
|
||||
from crc.models.user import UserModel
|
||||
|
@ -32,6 +30,10 @@ class BaseTest(unittest.TestCase):
|
|||
efficiently when we have a database in place.
|
||||
"""
|
||||
|
||||
if not app.config['TESTING']:
|
||||
raise (Exception("INVALID TEST CONFIGURATION. This is almost always in import order issue."
|
||||
"The first class to import in each test should be the base_test.py file."))
|
||||
|
||||
auths = {}
|
||||
test_uid = "dhf8r"
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1v1rp1q" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.3">
|
||||
<bpmn:process id="empty_workflow" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>SequenceFlow_0lvudp8</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_0lvudp8" sourceRef="StartEvent_1" targetRef="EndEvent_0q4qzl9" />
|
||||
<bpmn:endEvent id="EndEvent_0q4qzl9">
|
||||
<bpmn:incoming>SequenceFlow_0lvudp8</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="empty_workflow">
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_0lvudp8_di" bpmnElement="SequenceFlow_0lvudp8">
|
||||
<di:waypoint x="215" y="117" />
|
||||
<di:waypoint x="432" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="179" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="EndEvent_0q4qzl9_di" bpmnElement="EndEvent_0q4qzl9">
|
||||
<dc:Bounds x="432" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
|
@ -0,0 +1,14 @@
|
|||
from tests.base_test import BaseTest
|
||||
|
||||
|
||||
class TestApprovals(BaseTest):
|
||||
|
||||
def test_list_approvals(self):
|
||||
rv = self.app.get('/v1.0/approval', headers=self.logged_in_headers())
|
||||
self.assert_success(rv)
|
||||
|
||||
def test_update_approval(self):
|
||||
rv = self.app.put('/v1.0/approval/1',
|
||||
headers=self.logged_in_headers(),
|
||||
data={})
|
||||
self.assert_success(rv)
|
|
@ -1,7 +1,7 @@
|
|||
from unittest.mock import patch
|
||||
|
||||
from crc.services.protocol_builder import ProtocolBuilderService
|
||||
from tests.base_test import BaseTest
|
||||
from crc.services.protocol_builder import ProtocolBuilderService
|
||||
|
||||
|
||||
class TestProtocolBuilder(BaseTest):
|
||||
|
@ -10,6 +10,7 @@ class TestProtocolBuilder(BaseTest):
|
|||
|
||||
@patch('crc.services.protocol_builder.requests.get')
|
||||
def test_get_studies(self, mock_get):
|
||||
ProtocolBuilderService.ENABLED = True
|
||||
mock_get.return_value.ok = True
|
||||
mock_get.return_value.text = self.protocol_builder_response('user_studies.json')
|
||||
response = ProtocolBuilderService.get_studies(self.test_uid)
|
||||
|
@ -17,6 +18,7 @@ class TestProtocolBuilder(BaseTest):
|
|||
|
||||
@patch('crc.services.protocol_builder.requests.get')
|
||||
def test_get_investigators(self, mock_get):
|
||||
ProtocolBuilderService.ENABLED = True
|
||||
mock_get.return_value.ok = True
|
||||
mock_get.return_value.text = self.protocol_builder_response('investigators.json')
|
||||
response = ProtocolBuilderService.get_investigators(self.test_study_id)
|
||||
|
@ -28,6 +30,7 @@ class TestProtocolBuilder(BaseTest):
|
|||
|
||||
@patch('crc.services.protocol_builder.requests.get')
|
||||
def test_get_required_docs(self, mock_get):
|
||||
ProtocolBuilderService.ENABLED = True
|
||||
mock_get.return_value.ok = True
|
||||
mock_get.return_value.text = self.protocol_builder_response('required_docs.json')
|
||||
response = ProtocolBuilderService.get_required_docs(self.test_study_id)
|
||||
|
@ -37,6 +40,7 @@ class TestProtocolBuilder(BaseTest):
|
|||
|
||||
@patch('crc.services.protocol_builder.requests.get')
|
||||
def test_get_details(self, mock_get):
|
||||
ProtocolBuilderService.ENABLED = True
|
||||
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)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import json
|
||||
from tests.base_test import BaseTest
|
||||
from datetime import datetime, timezone
|
||||
from unittest.mock import patch
|
||||
|
||||
|
@ -8,7 +9,7 @@ from crc.models.protocol_builder import ProtocolBuilderStatus, \
|
|||
from crc.models.stats import TaskEventModel
|
||||
from crc.models.study import StudyModel, StudySchema
|
||||
from crc.models.workflow import WorkflowSpecModel, WorkflowModel, WorkflowSpecCategoryModel
|
||||
from tests.base_test import BaseTest
|
||||
from crc.services.protocol_builder import ProtocolBuilderService
|
||||
|
||||
|
||||
class TestStudyApi(BaseTest):
|
||||
|
@ -38,24 +39,12 @@ class TestStudyApi(BaseTest):
|
|||
study = session.query(StudyModel).first()
|
||||
self.assertIsNotNone(study)
|
||||
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_investigators') # mock_studies
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_study_details') # mock_details
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_studies') # mock_studies
|
||||
def test_get_study(self, mock_studies, mock_details, mock_docs, mock_investigators):
|
||||
def test_get_study(self):
|
||||
"""Generic test, but pretty detailed, in that the study should return a categorized list of workflows
|
||||
This starts with out loading the example data, to show that all the bases are covered from ground 0."""
|
||||
|
||||
# Mock Protocol Builder responses
|
||||
studies_response = self.protocol_builder_response('user_studies.json')
|
||||
mock_studies.return_value = ProtocolBuilderStudySchema(many=True).loads(studies_response)
|
||||
details_response = self.protocol_builder_response('study_details.json')
|
||||
mock_details.return_value = json.loads(details_response)
|
||||
docs_response = self.protocol_builder_response('required_docs.json')
|
||||
mock_docs.return_value = json.loads(docs_response)
|
||||
investigators_response = self.protocol_builder_response('investigators.json')
|
||||
mock_investigators.return_value = json.loads(investigators_response)
|
||||
|
||||
"""NOTE: The protocol builder is not enabled or mocked out. As the master workflow (which is empty),
|
||||
and the test workflow do not need it, and it is disabled in the configuration."""
|
||||
new_study = self.add_test_study()
|
||||
new_study = session.query(StudyModel).filter_by(id=new_study["id"]).first()
|
||||
# Add a category
|
||||
|
@ -65,7 +54,7 @@ class TestStudyApi(BaseTest):
|
|||
# Create a workflow specification
|
||||
self.create_workflow("random_fact", study=new_study, category_id=new_category.id)
|
||||
# Assure there is a master specification, and it has the lookup files it needs.
|
||||
spec = self.load_test_spec("top_level_workflow", master_spec=True)
|
||||
spec = self.load_test_spec("empty_workflow", master_spec=True)
|
||||
self.create_reference_document()
|
||||
|
||||
api_response = self.app.get('/v1.0/study/%i' % new_study.id,
|
||||
|
@ -126,6 +115,9 @@ class TestStudyApi(BaseTest):
|
|||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_study_details') # mock_details
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_studies') # mock_studies
|
||||
def test_get_all_studies(self, mock_studies, mock_details, mock_docs, mock_investigators):
|
||||
# Enable the protocol builder for these tests, as the master_workflow and other workflows
|
||||
# depend on using the PB for data.
|
||||
ProtocolBuilderService.ENABLED = True
|
||||
self.load_example_data()
|
||||
s = StudyModel(
|
||||
id=54321, # This matches one of the ids from the study_details_json data.
|
||||
|
@ -208,6 +200,7 @@ class TestStudyApi(BaseTest):
|
|||
self.assertEqual(study.sponsor, json_data['sponsor'])
|
||||
self.assertEqual(study.ind_number, json_data['ind_number'])
|
||||
|
||||
|
||||
def test_delete_study(self):
|
||||
self.load_example_data()
|
||||
study = session.query(StudyModel).first()
|
||||
|
|
|
@ -8,6 +8,7 @@ from crc.models.api_models import WorkflowApiSchema, MultiInstanceType, TaskSche
|
|||
from crc.models.file import FileModelSchema
|
||||
from crc.models.stats import TaskEventModel
|
||||
from crc.models.workflow import WorkflowStatus
|
||||
from crc.services.protocol_builder import ProtocolBuilderService
|
||||
from crc.services.workflow_service import WorkflowService
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
|
@ -302,6 +303,9 @@ class TestTasksApi(BaseTest):
|
|||
|
||||
@patch('crc.services.protocol_builder.requests.get')
|
||||
def test_multi_instance_task(self, mock_get):
|
||||
# Enable the protocol builder.
|
||||
ProtocolBuilderService.ENABLED = True
|
||||
|
||||
# This depends on getting a list of investigators back from the protocol builder.
|
||||
mock_get.return_value.ok = True
|
||||
mock_get.return_value.text = self.protocol_builder_response('investigators.json')
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
from crc import app
|
||||
from tests.base_test import BaseTest
|
||||
from crc import app
|
||||
|
||||
|
||||
class TestStudyApi(BaseTest):
|
||||
|
@ -22,11 +22,13 @@ class TestStudyApi(BaseTest):
|
|||
{"option": "Address", "selected": False},
|
||||
{"option": "Phone", "selected": True, "stored": ["Send or Transmit outside of UVA"]}]}
|
||||
with open(filepath, 'rb') as f:
|
||||
file_data = {'file': (f, 'my_new_file.bpmn')}
|
||||
rv = self.app.put('/v1.0/render_docx?data=%s' % json.dumps(template_data),
|
||||
file_data = {'file': (f, 'my_new_file.bpmn'), 'data': json.dumps(template_data)}
|
||||
rv = self.app.put('/v1.0/render_docx',
|
||||
data=file_data, follow_redirects=True,
|
||||
content_type='multipart/form-data')
|
||||
self.assert_success(rv)
|
||||
self.assertIsNotNone(rv.data)
|
||||
self.assertEquals('application/octet-stream', rv.content_type)
|
||||
|
||||
def test_list_scripts(self):
|
||||
rv = self.app.get('/v1.0/list_scripts')
|
||||
|
|
|
@ -20,21 +20,7 @@ class TestWorkflowSpecValidation(BaseTest):
|
|||
json_data = json.loads(rv.get_data(as_text=True))
|
||||
return ApiErrorSchema(many=True).load(json_data)
|
||||
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_investigators') # mock_studies
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_study_details') # mock_details
|
||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_studies') # mock_studies
|
||||
def test_successful_validation_of_test_workflows(self, mock_studies, mock_details, mock_docs, mock_investigators):
|
||||
|
||||
# Mock Protocol Builder responses
|
||||
studies_response = self.protocol_builder_response('user_studies.json')
|
||||
mock_studies.return_value = ProtocolBuilderStudySchema(many=True).loads(studies_response)
|
||||
details_response = self.protocol_builder_response('study_details.json')
|
||||
mock_details.return_value = json.loads(details_response)
|
||||
docs_response = self.protocol_builder_response('required_docs.json')
|
||||
mock_docs.return_value = json.loads(docs_response)
|
||||
investigators_response = self.protocol_builder_response('investigators.json')
|
||||
mock_investigators.return_value = json.loads(investigators_response)
|
||||
def test_successful_validation_of_test_workflows(self):
|
||||
|
||||
self.assertEqual(0, len(self.validate_workflow("parallel_tasks")))
|
||||
self.assertEqual(0, len(self.validate_workflow("decision_table")))
|
||||
|
|
Loading…
Reference in New Issue