Merge pull request #174 from sartography/dev

Dev --> Testing
This commit is contained in:
Aaron Louie 2020-08-03 11:00:35 -04:00 committed by GitHub
commit 432709c256
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 154 additions and 55 deletions

View File

@ -1086,9 +1086,9 @@ components:
user_uid:
type: string
example: dhf8r
protocol_builder_status:
status:
type: string
enum: ['incomplete', 'active', 'hold', 'open', 'abandoned']
enum: ['in_progress', 'hold', 'open_for_enrollment', 'abandoned']
example: done
sponsor:
type: string

View File

@ -6,7 +6,7 @@ from sqlalchemy.exc import IntegrityError
from crc import session
from crc.api.common import ApiError, ApiErrorSchema
from crc.models.protocol_builder import ProtocolBuilderStatus
from crc.models.study import StudySchema, StudyModel, Study
from crc.models.study import Study, StudyModel, StudySchema, StudyStatus
from crc.services.study_service import StudyService
from crc.services.user_service import UserService
@ -22,7 +22,7 @@ def add_study(body):
title=body['title'],
primary_investigator_id=body['primary_investigator_id'],
last_updated=datetime.now(),
protocol_builder_status=ProtocolBuilderStatus.active)
status=StudyStatus.in_progress)
session.add(study_model)
errors = StudyService._add_all_workflow_specs_to_study(study_model)

View File

@ -17,6 +17,7 @@ class ProtocolBuilderInvestigatorType(enum.Enum):
SCI = "Scientific Contact"
# Deprecated: Marked for removal
class ProtocolBuilderStatus(enum.Enum):
# • Active: found in PB and no HSR number and not hold
# • Hold: store boolean value in CR Connect (add to Study Model)

View File

@ -1,3 +1,7 @@
import datetime
import enum
import json
import marshmallow
from marshmallow import INCLUDE, fields
from marshmallow_enum import EnumField
@ -11,12 +15,26 @@ from crc.models.workflow import WorkflowSpecCategoryModel, WorkflowState, Workfl
WorkflowModel
class StudyStatus(enum.Enum):
in_progress = 'in_progress'
hold = 'hold'
open_for_enrollment = 'open_for_enrollment'
abandoned = 'abandoned'
class IrbStatus(enum.Enum):
incomplete_in_protocol_builder = 'incomplete in protocol builder'
completed_in_protocol_builder = 'completed in protocol builder'
hsr_assigned = 'hsr number assigned'
class StudyModel(db.Model):
__tablename__ = 'study'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
last_updated = db.Column(db.DateTime(timezone=True), default=func.now())
protocol_builder_status = db.Column(db.Enum(ProtocolBuilderStatus))
status = db.Column(db.Enum(StudyStatus))
irb_status = db.Column(db.Enum(IrbStatus))
primary_investigator_id = db.Column(db.String, nullable=True)
sponsor = db.Column(db.String, nullable=True)
hsr_number = db.Column(db.String, nullable=True)
@ -33,11 +51,13 @@ class StudyModel(db.Model):
self.user_uid = pbs.NETBADGEID
self.last_updated = pbs.DATE_MODIFIED
self.protocol_builder_status = ProtocolBuilderStatus.active
self.irb_status = IrbStatus.incomplete_in_protocol_builder
self.status = StudyStatus.in_progress
if pbs.HSRNUMBER:
self.protocol_builder_status = ProtocolBuilderStatus.open
self.irb_status = IrbStatus.hsr_assigned
self.status = StudyStatus.open_for_enrollment
if self.on_hold:
self.protocol_builder_status = ProtocolBuilderStatus.hold
self.status = StudyStatus.hold
class WorkflowMetadata(object):
@ -108,15 +128,15 @@ class CategorySchema(ma.Schema):
class Study(object):
def __init__(self, title, last_updated, primary_investigator_id, user_uid,
id=None,
protocol_builder_status=None,
id=None, status=None, irb_status=None,
sponsor="", hsr_number="", ind_number="", categories=[],
files=[], approvals=[], enrollment_date=None, **argsv):
self.id = id
self.user_uid = user_uid
self.title = title
self.last_updated = last_updated
self.protocol_builder_status = protocol_builder_status
self.status = status
self.irb_status = irb_status
self.primary_investigator_id = primary_investigator_id
self.sponsor = sponsor
self.hsr_number = hsr_number
@ -135,9 +155,29 @@ class Study(object):
return instance
def update_model(self, study_model: StudyModel):
for k,v in self.__dict__.items():
if not k.startswith('_'):
study_model.__dict__[k] = v
"""As the case for update was very reduced, it's mostly and specifically
updating only the study status and generating a history record
"""
status = StudyStatus(self.status)
study_model.last_updated = datetime.datetime.now()
study_model.status = status
if status == StudyStatus.open_for_enrollment:
study_model.enrollment_date = self.enrollment_date
# change = {
# 'status': ProtocolBuilderStatus(self.protocol_builder_status).value,
# 'comment': '' if not hasattr(self, 'comment') else self.comment,
# 'date': str(datetime.datetime.now())
# }
# if study_model.changes_history:
# changes_history = json.loads(study_model.changes_history)
# changes_history.append(change)
# else:
# changes_history = [change]
# study_model.changes_history = json.dumps(changes_history)
def model_args(self):
"""Arguments that can be passed into the Study Model to update it."""
@ -147,12 +187,33 @@ class Study(object):
return self_dict
class StudyForUpdateSchema(ma.Schema):
id = fields.Integer(required=False, allow_none=True)
status = EnumField(StudyStatus, by_value=True)
hsr_number = fields.String(allow_none=True)
sponsor = fields.String(allow_none=True)
ind_number = fields.String(allow_none=True)
enrollment_date = fields.DateTime(allow_none=True)
comment = fields.String(allow_none=True)
class Meta:
model = Study
unknown = INCLUDE
@marshmallow.post_load
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 StudySchema(ma.Schema):
id = fields.Integer(required=False, allow_none=True)
categories = fields.List(fields.Nested(CategorySchema), dump_only=True)
warnings = fields.List(fields.Nested(ApiErrorSchema), dump_only=True)
protocol_builder_status = EnumField(ProtocolBuilderStatus)
protocol_builder_status = EnumField(StudyStatus, by_value=True)
status = EnumField(StudyStatus, by_value=True)
hsr_number = fields.String(allow_none=True)
sponsor = fields.String(allow_none=True)
ind_number = fields.String(allow_none=True)

View File

@ -12,7 +12,7 @@ from crc.api.common import ApiError
from crc.models.file import FileModel, FileModelSchema, File
from crc.models.ldap import LdapSchema
from crc.models.protocol_builder import ProtocolBuilderStudy, ProtocolBuilderStatus
from crc.models.study import StudyModel, Study, Category, WorkflowMetadata
from crc.models.study import StudyModel, Study, StudyStatus, Category, WorkflowMetadata
from crc.models.task_event import TaskEventModel, TaskEvent
from crc.models.workflow import WorkflowSpecCategoryModel, WorkflowModel, WorkflowSpecModel, WorkflowState, \
WorkflowStatus
@ -64,7 +64,7 @@ class StudyService(object):
# Calling this line repeatedly is very very slow. It creates the
# master spec and runs it. Don't execute this for Abandoned studies, as
# we don't have the information to process them.
if study.protocol_builder_status != ProtocolBuilderStatus.abandoned:
if study.status != StudyStatus.abandoned:
status = StudyService.__get_study_status(study_model)
study.warnings = StudyService.__update_status_of_workflow_meta(workflow_metas, status)
@ -265,7 +265,7 @@ class StudyService(object):
for study in db_studies:
pb_study = next((pbs for pbs in pb_studies if pbs.STUDYID == study.id), None)
if not pb_study:
study.protocol_builder_status = ProtocolBuilderStatus.abandoned
study.status = StudyStatus.abandoned
db.session.commit()

View File

@ -0,0 +1,36 @@
"""empty message
Revision ID: 1c3f88dbccc3
Revises: 2e7b377cbc7b
Create Date: 2020-07-30 18:51:01.816284
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '1c3f88dbccc3'
down_revision = 'ab06a94e5d4c'
branch_labels = None
depends_on = None
def upgrade():
op.execute("CREATE TYPE irbstatus AS ENUM('incomplete_in_protocol_builder', 'completed_in_protocol_builder', 'hsr_assigned')")
op.execute("CREATE TYPE studystatus AS ENUM('in_progress', 'hold', 'open_for_enrollment', 'abandoned')")
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('study', sa.Column('irb_status', sa.Enum('incomplete_in_protocol_builder', 'completed_in_protocol_builder', 'hsr_assigned', name='irbstatus'), nullable=True))
op.add_column('study', sa.Column('status', sa.Enum('in_progress', 'hold', 'open_for_enrollment', 'abandoned', name='studystatus'), nullable=True))
op.drop_column('study', 'protocol_builder_status')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('study', sa.Column('protocol_builder_status', postgresql.ENUM('incomplete', 'active', 'hold', 'open', 'abandoned', name='protocolbuilderstatus'), autoincrement=False, nullable=True))
op.drop_column('study', 'status')
op.drop_column('study', 'irb_status')
# ### end Alembic commands ###
op.execute('DROP TYPE studystatus')
op.execute('DROP TYPE irbstatus')

View File

@ -15,9 +15,8 @@ from crc import app, db, session
from crc.models.api_models import WorkflowApiSchema, MultiInstanceType
from crc.models.approval import ApprovalModel, ApprovalStatus
from crc.models.file import FileModel, FileDataModel, CONTENT_TYPES
from crc.models.protocol_builder import ProtocolBuilderStatus
from crc.models.study import StudyModel
from crc.models.task_event import TaskEventModel
from crc.models.study import StudyModel, StudyStatus
from crc.models.user import UserModel
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecCategoryModel
from crc.services.file_service import FileService
@ -69,24 +68,24 @@ class BaseTest(unittest.TestCase):
studies = [
{
'id': 0,
'title': 'The impact of fried pickles on beer consumption in bipedal software developers.',
'last_updated': datetime.datetime.now(),
'protocol_builder_status': ProtocolBuilderStatus.active,
'primary_investigator_id': 'dhf8r',
'sponsor': 'Sartography Pharmaceuticals',
'ind_number': '1234',
'user_uid': 'dhf8r'
'id':0,
'title':'The impact of fried pickles on beer consumption in bipedal software developers.',
'last_updated':datetime.datetime.now(),
'status':StudyStatus.in_progress,
'primary_investigator_id':'dhf8r',
'sponsor':'Sartography Pharmaceuticals',
'ind_number':'1234',
'user_uid':'dhf8r'
},
{
'id': 1,
'title': 'Requirement of hippocampal neurogenesis for the behavioral effects of soft pretzels',
'last_updated': datetime.datetime.now(),
'protocol_builder_status': ProtocolBuilderStatus.active,
'primary_investigator_id': 'dhf8r',
'sponsor': 'Makerspace & Co.',
'ind_number': '5678',
'user_uid': 'dhf8r'
'id':1,
'title':'Requirement of hippocampal neurogenesis for the behavioral effects of soft pretzels',
'last_updated':datetime.datetime.now(),
'status':StudyStatus.in_progress,
'primary_investigator_id':'dhf8r',
'sponsor':'Makerspace & Co.',
'ind_number':'5678',
'user_uid':'dhf8r'
}
]
@ -258,7 +257,7 @@ class BaseTest(unittest.TestCase):
study = session.query(StudyModel).filter_by(user_uid=uid).filter_by(title=title).first()
if study is None:
user = self.create_user(uid=uid)
study = StudyModel(title=title, protocol_builder_status=ProtocolBuilderStatus.active,
study = StudyModel(title=title, status=StudyStatus.in_progress,
user_uid=user.uid, primary_investigator_id=primary_investigator_id)
session.add(study)
session.commit()

View File

@ -11,7 +11,7 @@ from crc.models.protocol_builder import ProtocolBuilderStatus, \
ProtocolBuilderStudySchema
from crc.models.approval import ApprovalStatus
from crc.models.task_event import TaskEventModel
from crc.models.study import StudyModel, StudySchema
from crc.models.study import StudyModel, StudySchema, StudyStatus
from crc.models.workflow import WorkflowSpecModel, WorkflowModel
from crc.services.file_service import FileService
from crc.services.workflow_processor import WorkflowProcessor
@ -24,16 +24,17 @@ class TestStudyApi(BaseTest):
"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.active,
"primary_investigator_id": "tmm2x",
"user_uid": "dhf8r",
}
def add_test_study(self):
study_schema = StudySchema().dump(self.TEST_STUDY)
study_schema['status'] = StudyStatus.in_progress.value
rv = self.app.post('/v1.0/study',
content_type="application/json",
headers=self.logged_in_headers(),
data=json.dumps(StudySchema().dump(self.TEST_STUDY)))
data=json.dumps(study_schema))
self.assert_success(rv)
return json.loads(rv.get_data(as_text=True))
@ -135,15 +136,16 @@ class TestStudyApi(BaseTest):
self.load_example_data()
study: StudyModel = session.query(StudyModel).first()
study.title = "Pilot Study of Fjord Placement for Single Fraction Outcomes to Cortisol Susceptibility"
study.protocol_builder_status = ProtocolBuilderStatus.active
study_schema = StudySchema().dump(study)
study_schema['status'] = StudyStatus.in_progress.value
rv = self.app.put('/v1.0/study/%i' % study.id,
content_type="application/json",
headers=self.logged_in_headers(),
data=json.dumps(StudySchema().dump(study)))
data=json.dumps(study_schema))
self.assert_success(rv)
json_data = json.loads(rv.get_data(as_text=True))
self.assertEqual(study.title, json_data['title'])
self.assertEqual(study.protocol_builder_status.name, json_data['protocol_builder_status'])
self.assertEqual(study.status.value, json_data['status'])
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_investigators') # mock_studies
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
@ -181,15 +183,15 @@ class TestStudyApi(BaseTest):
num_incomplete = 0
num_abandoned = 0
num_active = 0
num_in_progress = 0
num_open = 0
for study in json_data:
if study['protocol_builder_status'] == 'abandoned': # One study does not exist in user_studies.json
if study['status'] == 'abandoned': # One study does not exist in user_studies.json
num_abandoned += 1
if study['protocol_builder_status'] == 'active': # One study is marked complete without HSR Number
num_active += 1
if study['protocol_builder_status'] == 'open': # One study is marked complete and has an HSR Number
if study['status'] == 'in_progress': # One study is marked complete without HSR Number
num_in_progress += 1
if study['status'] == 'open_for_enrollment': # One study is marked complete and has an HSR Number
num_open += 1
db_studies_after = session.query(StudyModel).all()
@ -197,10 +199,10 @@ class TestStudyApi(BaseTest):
self.assertGreater(num_db_studies_after, num_db_studies_before)
self.assertEqual(num_abandoned, 1)
self.assertEqual(num_open, 1)
self.assertEqual(num_active, 2)
self.assertEqual(num_in_progress, 2)
self.assertEqual(num_incomplete, 0)
self.assertEqual(len(json_data), num_db_studies_after)
self.assertEqual(num_open + num_active + num_incomplete + num_abandoned, num_db_studies_after)
self.assertEqual(num_open + num_in_progress + num_incomplete + num_abandoned, num_db_studies_after)
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_investigators') # mock_studies
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
@ -228,7 +230,7 @@ class TestStudyApi(BaseTest):
json_data = json.loads(rv.get_data(as_text=True))
self.assertEqual(study.id, json_data['id'])
self.assertEqual(study.title, json_data['title'])
self.assertEqual(study.protocol_builder_status.name, json_data['protocol_builder_status'])
self.assertEqual(study.status.value, json_data['status'])
self.assertEqual(study.primary_investigator_id, json_data['primary_investigator_id'])
self.assertEqual(study.sponsor, json_data['sponsor'])
self.assertEqual(study.ind_number, json_data['ind_number'])

View File

@ -6,7 +6,7 @@ from tests.base_test import BaseTest
from crc import db, app
from crc.models.protocol_builder import ProtocolBuilderStatus
from crc.models.study import StudyModel
from crc.models.study import StudyModel, StudyStatus
from crc.models.user import UserModel
from crc.models.workflow import WorkflowModel, WorkflowStatus, \
WorkflowSpecCategoryModel
@ -40,7 +40,7 @@ class TestStudyService(BaseTest):
for study in db.session.query(StudyModel).all():
StudyService().delete_study(study.id)
study = StudyModel(title="My title", protocol_builder_status=ProtocolBuilderStatus.active, user_uid=user.uid)
study = StudyModel(title="My title", status=StudyStatus.in_progress, user_uid=user.uid)
db.session.add(study)
self.load_test_spec("random_fact", category_id=cat.id)

View File

@ -8,7 +8,7 @@ from tests.base_test import BaseTest
from crc import app, session
from crc.api.common import ApiError
from crc.models.protocol_builder import ProtocolBuilderStatus
from crc.models.study import StudySchema, StudyModel
from crc.models.study import StudySchema, StudyModel, StudyStatus
from crc.models.user import UserModel
@ -274,7 +274,7 @@ class TestAuthentication(BaseTest):
return {
"title": "blah",
"last_updated": datetime.now(tz=timezone.utc),
"protocol_builder_status": ProtocolBuilderStatus.active,
"status": StudyStatus.in_progress,
"primary_investigator_id": uid,
"user_uid": uid,
}