def test_get_user_with_spaces(self): user_info = LdapService.user_info(" LB3DP ") # Call this a second time, becuase the error we ran into wasn't that it wasn't possible to find it, # but that it attepts to add it to the database a second time with the same id. user_info = LdapService.user_info(" LB3DP ") self.assertIsNotNone(user_info) self.assertEqual("lb3dp", user_info.uid)
def test_delete_study_with_workflow_and_status_etc(self): self.load_example_data() workflow = session.query(WorkflowModel).first() stats1 = StudyEvent( study_id=workflow.study_id, status=StudyStatus.in_progress, comment='Some study status change event', event_type=StudyEventType.user, user_uid=self.users[0]['uid'], ) LdapService.user_info('dhf8r') # Assure that there is a dhf8r in ldap for StudyAssociated. email = EmailModel(subject="x", study_id=workflow.study_id) associate = StudyAssociated(study_id=workflow.study_id, uid=self.users[0]['uid']) event = StudyEvent(study_id=workflow.study_id) session.add_all([email, associate, event]) stats2 = TaskEventModel(study_id=workflow.study_id, workflow_id=workflow.id, user_uid=self.users[0]['uid']) session.add_all([stats1, stats2]) session.commit() rv = self.app.delete('/v1.0/study/%i' % workflow.study_id, headers=self.logged_in_headers()) self.assert_success(rv) del_study = session.query(StudyModel).filter(StudyModel.id == workflow.study_id).first() self.assertIsNone(del_study)
def update_approval(approval_id, approver_uid): """Update a specific approval NOTE: Actual update happens in the API layer, this funtion is currently in charge of only sending corresponding emails """ db_approval = session.query(ApprovalModel).get(approval_id) status = db_approval.status if db_approval: if status == ApprovalStatus.APPROVED.value: # second_approval = ApprovalModel().query.filter_by( # study_id=db_approval.study_id, workflow_id=db_approval.workflow_id, # status=ApprovalStatus.PENDING.value, version=db_approval.version).first() # if second_approval: # send rrp approval request for second approver ldap_service = LdapService() pi_user_info = ldap_service.user_info( db_approval.study.primary_investigator_id) approver_info = ldap_service.user_info(approver_uid) # send rrp submission mail_result = send_ramp_up_approved_email( '*****@*****.**', [pi_user_info.email_address], f'{approver_info.display_name} - ({approver_info.uid})') if mail_result: app.logger.error(mail_result, exc_info=True) elif status == ApprovalStatus.DECLINED.value: ldap_service = LdapService() pi_user_info = ldap_service.user_info( db_approval.study.primary_investigator_id) approver_info = ldap_service.user_info(approver_uid) # send rrp submission mail_result = send_ramp_up_denied_email( '*****@*****.**', [pi_user_info.email_address], f'{approver_info.display_name} - ({approver_info.uid})') if mail_result: app.logger.error(mail_result, exc_info=True) first_approval = ApprovalModel().query.filter_by( study_id=db_approval.study_id, workflow_id=db_approval.workflow_id, status=ApprovalStatus.APPROVED.value, version=db_approval.version).first() if first_approval: # Second approver denies first_approver_info = ldap_service.user_info( first_approval.approver_uid) approver_email = [ first_approver_info.email_address ] if first_approver_info.email_address else app.config[ 'FALLBACK_EMAILS'] # send rrp denied by second approver email to first approver mail_result = send_ramp_up_denied_email_to_approver( '*****@*****.**', approver_email, f'{pi_user_info.display_name} - ({pi_user_info.uid})', f'{approver_info.display_name} - ({approver_info.uid})' ) if mail_result: app.logger.error(mail_result, exc_info=True) return db_approval
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_model: StudyModel = None, do_status=False): """Returns a study model that contains all the workflows organized by category. IMPORTANT: This is intended to be a lightweight call, it should never involve loading up and executing all the workflows in a study to calculate information.""" if not study_model: study_model = session.query(StudyModel).filter_by( id=study_id).first() study = Study.from_model(study_model) study.create_user_display = LdapService.user_info( study.user_uid).display_name last_event: TaskEventModel = session.query(TaskEventModel) \ .filter_by(study_id=study_id, action='COMPLETE') \ .order_by(TaskEventModel.date.desc()).first() if last_event is None: study.last_activity_user = '******' study.last_activity_date = "" else: study.last_activity_user = LdapService.user_info( last_event.user_uid).display_name study.last_activity_date = last_event.date study.categories = StudyService.get_categories() workflow_metas = StudyService._get_workflow_metas(study_id) files = FileService.get_files_for_study(study.id) files = (File.from_models(model, FileService.get_file_data(model.id), DocumentService.get_dictionary()) for model in files) study.files = list(files) # 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.status != StudyStatus.abandoned: # this line is taking 99% of the time that is used in get_study. # see ticket #196 if do_status: # __get_study_status() runs the master workflow to generate the status dictionary status = StudyService._get_study_status(study_model) study.warnings = StudyService._update_status_of_workflow_meta( workflow_metas, status) # Group the workflows into their categories. for category in study.categories: category.workflows = { w for w in workflow_metas if w.category_id == category.id } return study
def set_users_info_in_task(self, task, args): if len(args) > 1: raise ApiError(code="invalid_argument", message="Ldap takes at most one argument, the " "UID for the person we want to look up.") if len(args) < 1: if UserService.has_user(): uid = UserService.current_user().uid else: uid = args[0] try: user_info = LdapService.user_info(uid) except ApiError as ae: app.logger.info(ae) return {} except Exception as e: app.logger.info(e) return {} else: user_info_dict = { "display_name": user_info.display_name, "given_name": user_info.given_name, "email_address": user_info.email_address, "telephone_number": user_info.telephone_number, "title": user_info.title, "department": user_info.department, "affiliation": user_info.affiliation, "sponsor_type": user_info.sponsor_type, "uid": user_info.uid, "proper_name": user_info.proper_name() } return user_info_dict
def get_users_info(self, task, args): if len(args) < 1: raise ApiError( code="missing_argument", message="Email script requires at least one argument. The " "name of the variable in the task data that contains user" "id to process. Multiple arguments are accepted.") emails = [] for arg in args: try: uid = task.workflow.script_engine.evaluate_expression( task, arg) except Exception as e: app.logger.error(f'Workflow engines could not parse {arg}', exc_info=True) continue user_info = LdapService.user_info(uid) email = user_info.email_address emails.append(user_info.email_address) if not isinstance(email, str): raise ApiError( code="invalid_argument", message= "The Email script requires at least 1 UID argument. The " "name of the variable in the task data that contains subject and" " user ids to process. This must point to an array or a string, but " "it currently points to a %s " % emails.__class__.__name__) return emails
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 get_approval_details(approval): """Returns a list of packed approval details, obtained from the task data sent during the workflow """ def extract_value(task, key): if key in task['data']: return pickle.loads(b64decode(task['data'][key]['__bytes__'])) else: return "" def find_task(uuid, task): if task['id']['__uuid__'] == uuid: return task for child in task['children']: task = find_task(uuid, child) if task: return task if approval.status != ApprovalStatus.APPROVED.value: return {} for related_approval in approval.related_approvals: if related_approval.status != ApprovalStatus.APPROVED.value: continue workflow = db.session.query(WorkflowModel).filter( WorkflowModel.id == approval.workflow_id).first() data = json.loads(workflow.bpmn_workflow_json) last_task = find_task(data['last_task']['__uuid__'], data['task_tree']) personnel = extract_value(last_task, 'personnel') training_val = extract_value(last_task, 'RequiredTraining') pi_supervisor = extract_value(last_task, 'PISupervisor')['value'] review_complete = 'AllRequiredTraining' in training_val pi_uid = workflow.study.primary_investigator_id pi_details = LdapService.user_info(pi_uid) details = { 'Supervisor': pi_supervisor, 'PI_Details': pi_details, 'Review': review_complete } details['person_details'] = [] details['person_details'].append(pi_details) for person in personnel: uid = person['PersonnelComputingID']['value'] details['person_details'].append(LdapService.user_info(uid)) return details
def from_model(cls, model: ApprovalModel): args = dict( (k, v) for k, v in model.__dict__.items() if not k.startswith('_')) instance = cls(**args) instance.related_approvals = [] instance.title = model.study.title if model.study else '' try: instance.approver = LdapService.user_info(model.approver_uid) instance.primary_investigator = LdapService.user_info( model.study.primary_investigator_id) except ApiError as ae: app.logger.error( f'Ldap lookup failed for approval record {model.id}', exc_info=True) doc_dictionary = FileService.get_doc_dictionary() instance.associated_files = [] for approval_file in model.approval_files: try: # fixme: This is slow because we are doing a ton of queries to find the irb code. extra_info = doc_dictionary[ approval_file.file_data.file_model.irb_doc_code] except: extra_info = None associated_file = {} associated_file['id'] = approval_file.file_data.file_model.id if extra_info: associated_file['name'] = '_'.join( (extra_info['category1'], approval_file.file_data.file_model.name)) associated_file['description'] = extra_info['description'] else: associated_file[ 'name'] = approval_file.file_data.file_model.name associated_file['description'] = 'No description available' associated_file[ 'name'] = '(' + model.study.primary_investigator_id + ')' + associated_file[ 'name'] associated_file[ 'content_type'] = approval_file.file_data.file_model.content_type instance.associated_files.append(associated_file) return instance
def create_user(self, uid="dhf8r", email="*****@*****.**", display_name="Hoopy Frood"): user = session.query(UserModel).filter(UserModel.uid == uid).first() if user is None: ldap_user = LdapService.user_info(uid) user = UserModel(uid=uid, ldap_info=ldap_user) session.add(user) session.commit() return user
def test_get_single_user(self): user_info = LdapService.user_info("lb3dp") self.assertIsNotNone(user_info) self.assertEqual("lb3dp", user_info.uid) self.assertEqual("Laura Barnes", user_info.display_name) self.assertEqual("Laura", user_info.given_name) self.assertEqual("*****@*****.**", user_info.email_address) self.assertEqual("+1 (434) 924-1723", user_info.telephone_number) self.assertEqual( "E0:Associate Professor of Systems and Information Engineering", user_info.title) self.assertEqual("E0:EN-Eng Sys and Environment", user_info.department) self.assertEqual("faculty", user_info.affiliation) self.assertEqual("Staff", user_info.sponsor_type)
def __init__(self, model: TaskEventModel, study: StudyModel, workflow: WorkflowMetadata): self.id = model.id self.study = study self.workflow = workflow self.user_uid = model.user_uid self.user_display = LdapService.user_info(model.user_uid).display_name self.action = model.action self.task_id = model.task_id self.task_title = model.task_title self.task_name = model.task_name self.task_type = model.task_type self.task_state = model.task_state self.task_lane = model.task_lane self.date = model.date
def test_current_user_status(self): self.load_example_data() rv = self.app.get('/v1.0/user') self.assert_failure(rv, 401) rv = self.app.get('/v1.0/user', headers=self.logged_in_headers()) self.assert_success(rv) # User must exist in the mock ldap responses. user = UserModel(uid="dhf8r", ldap_info=LdapService.user_info("dhf8r")) rv = self.app.get('/v1.0/user', headers=self.logged_in_headers( user, redirect_url='http://omg.edu/lolwut')) self.assert_success(rv) user_data = json.loads(rv.get_data(as_text=True)) self.assertTrue(user_data['is_admin'])
def create_user_with_study_and_workflow(self): # clear it all out. from example_data import ExampleDataLoader ExampleDataLoader.clean_db() # Assure some basic models are in place, This is a damn mess. Our database models need an overhaul to make # this easier - better relationship modeling is now critical. cat = WorkflowSpecCategoryModel(id=None, display_name="Approvals", display_order=0) db.session.add(cat) db.session.commit() self.load_test_spec("top_level_workflow", master_spec=True, category_id=cat.id) user = db.session.query(UserModel).filter( UserModel.uid == "dhf8r").first() if not user: ldap = LdapService.user_info('dhf8r') user = UserModel(uid="dhf8r", ldap_info=ldap) db.session.add(user) db.session.commit() else: for study in db.session.query(StudyModel).all(): StudyService().delete_study(study.id) 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) self.assertIsNotNone(study.id) workflow = WorkflowModel(workflow_spec_id="random_fact", study_id=study.id, status=WorkflowStatus.not_started, last_updated=datetime.utcnow()) db.session.add(workflow) db.session.commit() # Assure there is a master specification, one standard spec, and lookup tables. ExampleDataLoader().load_reference_documents() return user
def get_study_associates(study_id): """ gets all associated people for a study from the database """ study = db.session.query(StudyModel).filter( StudyModel.id == study_id).first() if study is None: raise ApiError('study_not_found', 'No study found with id = %d' % study_id) people = db.session.query(StudyAssociated).filter( StudyAssociated.study_id == study_id).all() ldap_info = LdapService.user_info(study.user_uid) owner = StudyAssociated(uid=study.user_uid, role='owner', send_email=True, access=True, ldap_info=ldap_info) people.append(owner) return people
def test_get_user_with_caps(self): user_info = LdapService.user_info("LB3DP") self.assertIsNotNone(user_info) self.assertEqual("lb3dp", user_info.uid)
def add_approval(study_id, workflow_id, approver_uid): """we might have multiple approvals for a workflow, so I would expect this method to get called multiple times for the same workflow. This will only add a new approval if no approval already exists for the approver_uid, unless the workflow has changed, at which point, it will CANCEL any pending approvals and create a new approval for the latest version of the workflow.""" # Find any existing approvals for this workflow. latest_approval_requests = db.session.query(ApprovalModel). \ filter(ApprovalModel.workflow_id == workflow_id). \ order_by(desc(ApprovalModel.version)) latest_approver_request = latest_approval_requests.filter( ApprovalModel.approver_uid == approver_uid).first() # Construct as hash of the latest files to see if things have changed since # the last approval. workflow = db.session.query(WorkflowModel).filter( WorkflowModel.id == workflow_id).first() workflow_data_files = FileService.get_workflow_data_files(workflow_id) current_data_file_ids = list(data_file.id for data_file in workflow_data_files) if len(current_data_file_ids) == 0: raise ApiError( "invalid_workflow_approval", "You can't create an approval for a workflow that has" "no files to approve in it.") # If an existing approval request exists and no changes were made, do nothing. # If there is an existing approval request for a previous version of the workflow # then add a new request, and cancel any waiting/pending requests. if latest_approver_request: request_file_ids = list( file.file_data_id for file in latest_approver_request.approval_files) current_data_file_ids.sort() request_file_ids.sort() other_approver = latest_approval_requests.filter( ApprovalModel.approver_uid != approver_uid).first() if current_data_file_ids == request_file_ids: return # This approval already exists or we're updating other approver. else: for approval_request in latest_approval_requests: if (approval_request.version == latest_approver_request.version and approval_request.status != ApprovalStatus.CANCELED.value): approval_request.status = ApprovalStatus.CANCELED.value db.session.add(approval_request) version = latest_approver_request.version + 1 else: version = 1 model = ApprovalModel(study_id=study_id, workflow_id=workflow_id, approver_uid=approver_uid, status=ApprovalStatus.PENDING.value, message="", date_created=datetime.now(), version=version) approval_files = ApprovalService._create_approval_files( workflow_data_files, model) # Check approvals count approvals_count = ApprovalModel().query.filter_by( study_id=study_id, workflow_id=workflow_id, version=version).count() db.session.add(model) db.session.add_all(approval_files) db.session.commit() # Send first email if approvals_count == 0: ldap_service = LdapService() pi_user_info = ldap_service.user_info( model.study.primary_investigator_id) approver_info = ldap_service.user_info(approver_uid) # send rrp submission mail_result = send_ramp_up_submission_email( '*****@*****.**', [pi_user_info.email_address], f'{approver_info.display_name} - ({approver_info.uid})') if mail_result: app.logger.error(mail_result, exc_info=True) # send rrp approval request for first approver # enhance the second part in case it bombs approver_email = [ approver_info.email_address ] if approver_info.email_address else app.config['FALLBACK_EMAILS'] mail_result = send_ramp_up_approval_request_first_review_email( '*****@*****.**', approver_email, f'{pi_user_info.display_name} - ({pi_user_info.uid})') if mail_result: app.logger.error(mail_result, exc_info=True)
def test_find_missing_user(self): try: user_info = LdapService.user_info("nosuch") self.assertFalse(True, "An API error should be raised.") except ApiError as ae: self.assertEqual("missing_ldap_record", ae.code)
def test_study_sponsors_script(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_associate") workflow_model = StudyService._create_workflow_model( study, workflow_spec_model) WorkflowService.test_spec("study_sponsors_associate") processor = WorkflowProcessor(workflow_model) processor.do_engine_steps() self.assertTrue(processor.bpmn_workflow.is_completed()) data = processor.next_task().data self.assertIn('sponsors', data) self.assertIn('out', data) print(data['out']) dhf8r_info = LdapSchema().dump(LdapService.user_info('dhf8r')) lb3dp_info = LdapSchema().dump(LdapService.user_info('lb3dp')) self.assertDictEqual( { 'uid': 'dhf8r', 'role': 'owner', 'send_email': True, 'access': True, 'ldap_info': dhf8r_info }, data['out'][1]) self.assertDictEqual( { 'uid': 'lb3dp', 'role': 'SuperDude', 'send_email': False, 'access': True, 'ldap_info': lb3dp_info }, data['out'][0]) self.assertDictEqual( { 'uid': 'lb3dp', 'role': 'SuperDude', 'send_email': False, 'access': True, 'ldap_info': lb3dp_info }, data['out2']) self.assertDictEqual( { 'uid': 'dhf8r', 'role': 'owner', 'send_email': True, 'access': True, 'ldap_info': dhf8r_info }, data['out3'][1]) self.assertDictEqual( { 'uid': 'lb3dp', 'role': 'SuperGal', 'send_email': False, 'access': True, 'ldap_info': lb3dp_info }, data['out3'][0]) self.assertDictEqual( { 'uid': 'lb3dp', 'role': 'SuperGal', 'send_email': False, 'access': True, 'ldap_info': lb3dp_info }, data['out4']) self.assertEqual(3, len(data['sponsors']))
def _run_ldap_query(query, value, limit): if value: return [LdapSchema().dump(LdapService.user_info(value))] else: users = LdapService.search_users(query, limit) return users