def delete_workflow_specification(spec_id): if spec_id is None: raise ApiError('unknown_spec', 'Please provide a valid Workflow Specification ID.') spec: WorkflowSpecModel = session.query(WorkflowSpecModel).filter_by( id=spec_id).first() if spec is None: raise ApiError( 'unknown_spec', 'The Workflow Specification "' + spec_id + '" is not recognized.') # Delete all items in the database related to the deleted workflow spec. files = session.query(FileModel).filter_by(workflow_spec_id=spec_id).all() for file in files: FileService.delete_file(file.id) session.query(TaskEventModel).filter( TaskEventModel.workflow_spec_id == spec_id).delete() # Delete all events and workflow models related to this specification for workflow in session.query(WorkflowModel).filter_by( workflow_spec_id=spec_id): StudyService.delete_workflow(workflow) session.query(WorkflowSpecModel).filter_by(id=spec_id).delete() session.commit()
def do_task(self, task, study_id, workflow_id, *args, **kwargs): self.check_args(args, 2) prefix = None if len(args) > 1: prefix = args[1] cmd = args[0] # study_info = {} # if self.__class__.__name__ in task.data: # study_info = task.data[self.__class__.__name__] retval = None if cmd == 'info': study = session.query(StudyModel).filter_by(id=study_id).first() schema = StudySchema() retval = schema.dump(study) if cmd == 'investigators': retval = StudyService().get_investigators(study_id) if cmd == 'roles': retval = StudyService().get_investigators(study_id, all=True) if cmd == 'details': details = self.pb.get_study_details(study_id) if len(details) > 0: retval = details[0] else: retval = None if cmd == 'sponsors': retval = self.pb.get_sponsors(study_id) if cmd == 'documents': retval = StudyService().get_documents_status(study_id) return self.box_it(retval, prefix)
def add_study(body): """Or any study like object. Body should include a title, and primary_investigator_id """ if 'primary_investigator_id' not in body: raise ApiError( "missing_pi", "Can't create a new study without a Primary Investigator.") if 'title' not in body: raise ApiError("missing_title", "Can't create a new study without a title.") study_model = StudyModel( user_uid=UserService.current_user().uid, title=body['title'], primary_investigator_id=body['primary_investigator_id'], last_updated=datetime.utcnow(), status=StudyStatus.in_progress) session.add(study_model) StudyService.add_study_update_event(study_model, status=StudyStatus.in_progress, event_type=StudyEventType.user, user_uid=g.user.uid) errors = StudyService._add_all_workflow_specs_to_study(study_model) session.commit() study = StudyService().get_study(study_model.id, do_status=True) study_data = StudySchema().dump(study) study_data["errors"] = ApiErrorSchema(many=True).dump(errors) return study_data
def test_get_required_docs(self, mock_docs): app.config['PB_ENABLED'] = True # mock out the protocol builder docs_response = self.protocol_builder_response('required_docs.json') mock_docs.return_value = json.loads(docs_response) user = self.create_user_with_study_and_workflow() studies = StudyService.get_studies_for_user(user) study = studies[0] study_service = StudyService() documents = study_service.get_documents_status( study_id=study.id) # Mocked out, any random study id works. self.assertIsNotNone(documents) self.assertTrue("UVACompl_PRCAppr" in documents.keys()) self.assertEqual("UVACompl_PRCAppr", documents["UVACompl_PRCAppr"]['code']) self.assertEqual("UVA Compliance / PRC Approval", documents["UVACompl_PRCAppr"]['display_name']) self.assertEqual("Cancer Center's PRC Approval Form", documents["UVACompl_PRCAppr"]['description']) self.assertEqual("UVA Compliance", documents["UVACompl_PRCAppr"]['category1']) self.assertEqual("PRC Approval", documents["UVACompl_PRCAppr"]['category2']) self.assertEqual("", documents["UVACompl_PRCAppr"]['category3']) self.assertEqual("CRC", documents["UVACompl_PRCAppr"]['Who Uploads?']) self.assertEqual(0, documents["UVACompl_PRCAppr"]['count']) self.assertEqual(True, documents["UVACompl_PRCAppr"]['required']) self.assertEqual('6', documents["UVACompl_PRCAppr"]['id'])
def delete_study(study_id): try: StudyService.delete_study(study_id) except IntegrityError as ie: session.rollback() message = "Failed to delete Study #%i due to an Integrity Error: %s" % ( study_id, str(ie)) raise ApiError(code="study_integrity_error", message=message)
def run_update_status(self, status): # shared code self.load_example_data() study_model = session.query(StudyModel).first() workflow_metas = StudyService._get_workflow_metas(study_model.id) warnings = StudyService._update_status_of_workflow_meta( workflow_metas, status) return workflow_metas, warnings
def delete_test_data(): for study in db.session.query(StudyModel).filter( StudyModel.user_uid == "test"): StudyService.delete_study(study.id) db.session.commit() user = db.session.query(UserModel).filter_by(uid="test").first() if user: db.session.delete(user)
def test_total_tasks_updated(self, mock_docs): """Assure that as a users progress is available when getting a list of studies for that user.""" app.config['PB_ENABLED'] = True docs_response = self.protocol_builder_response('required_docs.json') mock_docs.return_value = json.loads(docs_response) user = self.create_user_with_study_and_workflow() # The load example data script should set us up a user and at least one study, one category, and one workflow. studies = StudyService.get_studies_for_user(user) self.assertTrue(len(studies) == 1) self.assertTrue(len(studies[0].categories) == 1) self.assertTrue(len(studies[0].categories[0].workflows) == 1) workflow = next(iter( studies[0].categories[0].workflows)) # Workflows is a set. # workflow should not be started, and it should have 0 completed tasks, and 0 total tasks. self.assertEqual(WorkflowStatus.not_started, workflow.status) self.assertEqual(0, workflow.total_tasks) self.assertEqual(0, workflow.completed_tasks) # Initialize the Workflow with the workflow processor. workflow_model = db.session.query(WorkflowModel).filter( WorkflowModel.id == workflow.id).first() processor = WorkflowProcessor(workflow_model) # Assure the workflow is now started, and knows the total and completed tasks. studies = StudyService.get_studies_for_user(user) workflow = next(iter( studies[0].categories[0].workflows)) # Workflows is a set. # self.assertEqual(WorkflowStatus.user_input_required, workflow.status) self.assertTrue(workflow.total_tasks > 0) self.assertEqual(0, workflow.completed_tasks) self.assertIsNotNone(workflow.spec_version) # Complete a task task = processor.next_task() processor.complete_task(task) processor.save() # Assure the workflow has moved on to the next task. studies = StudyService.get_studies_for_user(user) workflow = next(iter( studies[0].categories[0].workflows)) # Workflows is a set. self.assertEqual(1, workflow.completed_tasks) # Get approvals approvals = StudyService.get_approvals(studies[0].id) self.assertGreater(len(approvals), 0) self.assertIsNotNone(approvals[0]['display_order'])
def user_studies(): """Returns all the studies associated with the current user. """ user = UserService.current_user(allow_admin_impersonate=True) StudyService.synch_with_protocol_builder_if_enabled(user) studies = StudyService().get_studies_for_user(user) if len(studies) == 0: studies = StudyService().get_studies_for_user(user, include_invalid=True) if len(studies) > 0: message = f"All studies associated with User: {user.uid} failed study validation" raise ApiError(code="study_integrity_error", message=message) results = StudySchema(many=True).dump(studies) return results
def load_example_data(self, use_crc_data=False, use_rrt_data=False): """use_crc_data will cause this to load the mammoth collection of documents we built up developing crc, use_rrt_data will do the same for hte rrt project, otherwise it depends on a small setup for running tests.""" from example_data import ExampleDataLoader ExampleDataLoader.clean_db() # If in production mode, only add the first user. if app.config['PRODUCTION']: ldap_info = LdapService.user_info(self.users[0]['uid']) session.add( UserModel(uid=self.users[0]['uid'], ldap_info=ldap_info)) else: for user_json in self.users: ldap_info = LdapService.user_info(user_json['uid']) session.add( UserModel(uid=user_json['uid'], ldap_info=ldap_info)) if use_crc_data: ExampleDataLoader().load_all() elif use_rrt_data: ExampleDataLoader().load_rrt() else: ExampleDataLoader().load_test_data() session.commit() for study_json in self.studies: study_model = StudyModel(**study_json) session.add(study_model) StudyService._add_all_workflow_specs_to_study(study_model) session.commit() update_seq = f"ALTER SEQUENCE %s RESTART WITH %s" % ( StudyModel.__tablename__ + '_id_seq', study_model.id + 1) print("Update Sequence." + update_seq) session.execute(update_seq) session.flush() specs = session.query(WorkflowSpecModel).all() self.assertIsNotNone(specs) for spec in specs: files = session.query(FileModel).filter_by( workflow_spec_id=spec.id).all() self.assertIsNotNone(files) self.assertGreater(len(files), 0) for file in files: # file_data = session.query(FileDataModel).filter_by(file_model_id=file.id).all() file_data = SpecFileService().get_spec_file_data(file.id).data self.assertIsNotNone(file_data) self.assertGreater(len(file_data), 0)
def get_study(study_id): study = StudyService.get_study(study_id) if (study is None): raise ApiError("unknown_study", 'The study "' + study_id + '" is not recognized.', status_code=404) return StudySchema().dump(study)
def test_file_data_set_changes_irb_code(self, mock_get): mock_get.return_value.ok = True mock_get.return_value.text = self.protocol_builder_response( 'required_docs.json') self.load_example_data() study = session.query(StudyModel).first() workflow_spec_model = self.load_test_spec("two_forms") workflow_model = StudyService._create_workflow_model( study, workflow_spec_model) irb_code = "UVACompl_PRCAppr" # The first file referenced in pb required docs. file = FileService.add_workflow_file(workflow_id=workflow_model.id, task_spec_name='TaskSpec01', name="anything.png", content_type="text", binary_data=b'1234', irb_doc_code=irb_code) processor = WorkflowProcessor(workflow_model) task = processor.next_task() FileDataSet().do_task(task, study.id, workflow_model.id, key="irb_code", value="Study_App_Doc", file_id=file.id) docs = StudyInfo().do_task(task, study.id, workflow_model.id, "documents") self.assertTrue(isinstance(docs, Box)) self.assertEqual(1, len(docs.Study_App_Doc.files)) self.assertEqual("Study_App_Doc", docs.Study_App_Doc.files[0].data_store.irb_code)
def test_get_documents_has_file_details(self, mock_docs): # mock out the protocol builder docs_response = self.protocol_builder_response('required_docs.json') mock_docs.return_value = json.loads(docs_response) user = self.create_user_with_study_and_workflow() # Add a document to the study with the correct code. workflow = self.create_workflow('docx') irb_code = "UVACompl_PRCAppr" # The first file referenced in pb required docs. FileService.add_workflow_file(workflow_id=workflow.id, name="anything.png", content_type="text", binary_data=b'1234', irb_doc_code=irb_code) docs = StudyService().get_documents_status(workflow.study_id) self.assertIsNotNone(docs) self.assertEqual("not_started", docs["UVACompl_PRCAppr"]['status']) self.assertEqual(1, docs["UVACompl_PRCAppr"]['count']) self.assertIsNotNone(docs["UVACompl_PRCAppr"]['files'][0]) self.assertIsNotNone(docs["UVACompl_PRCAppr"]['files'][0]['file_id']) self.assertEqual(workflow.id, docs["UVACompl_PRCAppr"]['files'][0]['workflow_id'])
def do_task(self, task, study_id, workflow_id, *args, **kwargs): if len(args) == 0: access_list = [] else: access_list = args[0] self.validate_arg(access_list) return StudyService.update_study_associates(study_id, access_list)
def test_get_personnel_roles(self, mock_docs): self.load_example_data() # mock out the protocol builder docs_response = self.protocol_builder_response('investigators.json') mock_docs.return_value = json.loads(docs_response) workflow = self.create_workflow( 'docx') # The workflow really doesnt matter in this case. investigators = StudyService().get_investigators(workflow.study_id, all=True) self.assertEqual(10, len(investigators)) # dhf8r is in the ldap mock data. self.assertEqual("dhf8r", investigators['PI']['user_id']) self.assertEqual("Dan Funk", investigators['PI']['display_name']) # Data from ldap self.assertEqual("Primary Investigator", investigators['PI']['label']) # Data from xls file. self.assertEqual("Always", investigators['PI']['display']) # Data from xls file. # asd3v is not in ldap, so an error should be returned. self.assertEqual("asd3v", investigators['DC']['user_id']) self.assertEqual("Unable to locate a user with id asd3v in LDAP", investigators['DC']['error']) # Data from ldap # No value is provided for Department Chair self.assertIsNone(investigators['DEPT_CH']['user_id'])
def test_validate_returns_error_if_reference_files_do_not_exist( self, mock_get): mock_get.return_value.ok = True mock_get.return_value.text = self.protocol_builder_response( 'required_docs.json') self.load_example_data() self.create_reference_document() study = session.query(StudyModel).first() workflow_spec_model = self.load_test_spec("two_forms") workflow_model = StudyService._create_workflow_model( study, workflow_spec_model) processor = WorkflowProcessor(workflow_model) task = processor.next_task() # Remove the reference file. file_model = db.session.query(FileModel). \ filter(FileModel.is_reference == True). \ filter(FileModel.name == FileService.DOCUMENT_LIST).first() if file_model: db.session.query(FileDataModel).filter( FileDataModel.file_model_id == file_model.id).delete() db.session.query(FileModel).filter( FileModel.id == file_model.id).delete() db.session.commit() db.session.flush() with self.assertRaises(ApiError): StudyInfo().do_task_validate_only(task, study.id, "documents")
def test_get_study_personnel(self, mock_docs): self.load_example_data() # mock out the protocol builder docs_response = self.protocol_builder_response('investigators.json') mock_docs.return_value = json.loads(docs_response) workflow = self.create_workflow( 'docx') # The workflow really doesnt matter in this case. investigators = StudyService().get_investigators(workflow.study_id, all=False) self.assertEqual(5, len(investigators)) # dhf8r is in the ldap mock data. self.assertEqual("dhf8r", investigators['PI']['user_id']) self.assertEqual("Dan Funk", investigators['PI']['display_name']) # Data from ldap self.assertEqual("Primary Investigator", investigators['PI']['label']) # Data from xls file. self.assertEqual("Always", investigators['PI']['display']) # Data from xls file. # Both Alex and Aaron are SI, and both should be returned. self.assertEqual("ajl2j", investigators['SI']['user_id']) self.assertEqual("cah3us", investigators['SI_2']['user_id'])
def test_study_sponsors_script_ensure_delete(self, mock_get): mock_get.return_value.ok = True mock_get.return_value.text = self.protocol_builder_response( 'sponsors.json') flask.g.user = UserModel(uid='dhf8r') app.config['PB_ENABLED'] = True self.load_example_data() study = session.query(StudyModel).first() workflow_spec_model = self.load_test_spec( "study_sponsors_associates_delete") workflow_model = StudyService._create_workflow_model( study, workflow_spec_model) WorkflowService.test_spec("study_sponsors_associates_delete") processor = WorkflowProcessor(workflow_model) processor.do_engine_steps() # change user and make sure we can access the study flask.g.user = UserModel(uid='lb3dp') flask.g.token = 'my spiffy token' app.config['PB_ENABLED'] = False output = user_studies() self.assertEqual(len(output), 0) flask.g.token = 'my spiffy token' app.config['PB_ENABLED'] = False output = user_studies() self.assertEqual(len(output), 0)
def test_get_all_studies(self): user = self.create_user_with_study_and_workflow() study = db.session.query(StudyModel).filter_by( user_uid=user.uid).first() self.assertIsNotNone(study) # Add a document to the study with the correct code. workflow1 = self.create_workflow('docx', study=study) workflow2 = self.create_workflow('empty_workflow', study=study) # Add files to both workflows. FileService.add_workflow_file(workflow_id=workflow1.id, name="anything.png", content_type="text", binary_data=b'1234', irb_doc_code="UVACompl_PRCAppr") FileService.add_workflow_file(workflow_id=workflow1.id, name="anything.png", content_type="text", binary_data=b'1234', irb_doc_code="AD_Consent_Model") FileService.add_workflow_file(workflow_id=workflow2.id, name="anything.png", content_type="text", binary_data=b'1234', irb_doc_code="UVACompl_PRCAppr") studies = StudyService().get_all_studies_with_files() self.assertEqual(1, len(studies)) self.assertEqual(3, len(studies[0].files))
def test_file_data_set_invalid_irb_code_fails(self, mock_get): mock_get.return_value.ok = True mock_get.return_value.text = self.protocol_builder_response( 'required_docs.json') self.load_example_data() study = session.query(StudyModel).first() workflow_spec_model = self.load_test_spec("two_forms") workflow_model = StudyService._create_workflow_model( study, workflow_spec_model) irb_code = "UVACompl_PRCAppr" # The first file referenced in pb required docs. file = FileService.add_workflow_file(workflow_id=workflow_model.id, task_spec_name='Activity01', name="anything.png", content_type="text", binary_data=b'1234', irb_doc_code=irb_code) processor = WorkflowProcessor(workflow_model) task = processor.next_task() with self.assertRaises(ApiError): FileDataSet().do_task(task, study.id, workflow_model.id, key="irb_code", value="My_Pretty_Pony", file_id=file.id)
def setUp(self): self.load_example_data() self.study = session.query(StudyModel).first() self.workflow_spec_model = self.load_test_spec("two_forms") self.workflow_model = StudyService._create_workflow_model( self.study, self.workflow_spec_model) self.processor = WorkflowProcessor(self.workflow_model) self.task = self.processor.next_task()
def test_study_associates(self): user = self.create_user_with_study_and_workflow() study = db.session.query(StudyModel).first() associates = StudyService.get_study_associates(study.id) self.assertEquals(1, len(associates)) assoc_json = StudyAssociatedSchema(many=True).dump(associates) print(assoc_json) self.assertEquals("Dan", assoc_json[0]['ldap_info']['given_name'])
def test_get_user_studies(self, mock_details): details_response = self.protocol_builder_response('study_details.json') mock_details.return_value = json.loads(details_response) user = self.create_user_with_study_and_workflow() studies = StudyService().get_studies_for_user(user) # study_details has a valid REVIEW_TYPE, so we should get 1 study back self.assertEqual(1, len(studies))
def get_associated_emails(study_id): associated_emails = [] associates = StudyService.get_study_associates(study_id) for associate in associates: if associate.send_email is True: user_info = LdapService.user_info(associate.uid) associated_emails.append(user_info.email_address) return associated_emails
def load_example_data(self, use_crc_data=False, use_rrt_data=False): """use_crc_data will cause this to load the mammoth collection of documents we built up developing crc, use_rrt_data will do the same for hte rrt project, otherwise it depends on a small setup for running tests.""" from example_data import ExampleDataLoader ExampleDataLoader.clean_db() if use_crc_data: ExampleDataLoader().load_all() elif use_rrt_data: ExampleDataLoader().load_rrt() else: ExampleDataLoader().load_test_data() for user_json in self.users: db.session.add(UserModel(**user_json)) db.session.commit() for study_json in self.studies: study_model = StudyModel(**study_json) db.session.add(study_model) StudyService._add_all_workflow_specs_to_study(study_model) db.session.execute(Sequence(StudyModel.__tablename__ + '_id_seq')) db.session.commit() db.session.flush() specs = session.query(WorkflowSpecModel).all() self.assertIsNotNone(specs) for spec in specs: files = session.query(FileModel).filter_by( workflow_spec_id=spec.id).all() self.assertIsNotNone(files) self.assertGreater(len(files), 0) for spec in specs: files = session.query(FileModel).filter_by( workflow_spec_id=spec.id).all() self.assertIsNotNone(files) self.assertGreater(len(files), 0) for file in files: file_data = session.query(FileDataModel).filter_by( file_model_id=file.id).all() self.assertIsNotNone(file_data) self.assertGreater(len(file_data), 0)
def do_task(self, task, study_id, workflow_id, *args, **kwargs): if len(args) < 1: raise ApiError('no_user_id_specified', 'A uva uid is the sole argument to this function') if not isinstance(args[0], str): raise ApiError('argument_should_be_string', 'A uva uid is always a string, please check type') associate = StudyService.get_study_associate(study_id=study_id, uid=args[0]) return StudyAssociatedSchema().dump(associate)
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs): study = StudyService.get_study(study_id) if study: return {"DETAIL": "Passed validation.", "STATUS": "No Error"} else: raise ApiError.from_task( code='bad_study', message=f'No study for study_id {study_id}', task=task)
def test_email_script_associated(self): workflow = self.create_workflow('email_script') workflow_api = self.get_workflow_api(workflow) # Only dhf8r is in testing DB. # We want to test multiple associates, and lb3dp is already in testing LDAP self.create_user(uid='lb3dp', email='*****@*****.**', display_name='Laura Barnes') StudyService.update_study_associates(workflow.study_id, [{ 'uid': 'dhf8r', 'role': 'Chief Bee Keeper', 'send_email': True, 'access': True }, { 'uid': 'lb3dp', 'role': 'Chief Cat Herder', 'send_email': True, 'access': True }]) first_task = workflow_api.next_task with mail.record_messages() as outbox: self.complete_form( workflow, first_task, { 'subject': 'My Test Subject', 'recipients': ['*****@*****.**', 'associated'] }) self.assertEqual(1, len(outbox)) self.assertIn(outbox[0].recipients[0], [ '*****@*****.**', '*****@*****.**', '*****@*****.**' ]) self.assertIn(outbox[0].recipients[1], [ '*****@*****.**', '*****@*****.**', '*****@*****.**' ]) self.assertIn(outbox[0].recipients[2], [ '*****@*****.**', '*****@*****.**', '*****@*****.**' ])
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs): """For validation only, pretend no results come back from pb""" self.check_args(args) # Assure the reference file exists (a bit hacky, but we want to raise this error early, and cleanly.) FileService.get_reference_file_data(FileService.DOCUMENT_LIST) FileService.get_reference_file_data(FileService.INVESTIGATOR_LIST) data = { "study":{ "info": { "id": 12, "title": "test", "primary_investigator_id":21, "user_uid": "dif84", "sponsor": "sponsor", "ind_number": "1234", "inactive": False }, "investigators": { "INVESTIGATORTYPE": "PI", "INVESTIGATORTYPEFULL": "Primary Investigator", "NETBADGEID": "dhf8r" }, "roles": { "INVESTIGATORTYPE": "PI", "INVESTIGATORTYPEFULL": "Primary Investigator", "NETBADGEID": "dhf8r" }, "details": { "IS_IND": 0, "IS_IDE": 0, "IS_MULTI_SITE": 0, "IS_UVA_PI_MULTI": 0 }, "approvals": { "study_id": 12, "workflow_id": 321, "display_name": "IRB API Details", "name": "irb_api_details", "status": WorkflowStatus.not_started.value, "workflow_spec_id": "irb_api_details", }, 'protocol': { 'id': 0, } } } self.add_data_to_task(task=task, data=data["study"]) self.add_data_to_task(task, {"documents": StudyService().get_documents_status(study_id)})
def update_study(study_id, body): """Pretty limited, but allows manual modifications to the study status """ if study_id is None: raise ApiError('unknown_study', 'Please provide a valid Study ID.') study_model = session.query(StudyModel).filter_by(id=study_id).first() if study_model is None: raise ApiError('unknown_study', 'The study "' + study_id + '" is not recognized.') study: Study = StudyForUpdateSchema().load(body) status = StudyStatus(study.status) study_model.last_updated = datetime.utcnow() if study_model.status != status: study_model.status = status StudyService.add_study_update_event( study_model, status, StudyEventType.user, user_uid=UserService.current_user().uid if UserService.has_user() else None, comment='' if not hasattr(study, 'comment') else study.comment, ) if status == StudyStatus.open_for_enrollment: study_model.enrollment_date = study.enrollment_date session.add(study_model) session.commit() if status == StudyStatus.abandoned or status == StudyStatus.hold: WorkflowService.process_workflows_for_cancels(study_id) # Need to reload the full study to return it to the frontend study = StudyService.get_study(study_id) return StudySchema().dump(study)