diff --git a/crc/api/approval.py b/crc/api/approval.py index a44dfc5b..fd01e221 100644 --- a/crc/api/approval.py +++ b/crc/api/approval.py @@ -90,8 +90,8 @@ def get_approvals_for_study(study_id=None): return results -def get_health_attesting_csv(all_approvals=True): - records = ApprovalService.get_health_attesting_records(all_approvals) +def get_health_attesting_csv(): + records = ApprovalService.get_health_attesting_records() si = io.StringIO() cw = csv.writer(si) cw.writerows(records) @@ -105,67 +105,10 @@ def get_health_attesting_csv(all_approvals=True): def get_csv(): """A damn lie, it's a json file. A huge bit of a one-off for RRT, but 3 weeks of midnight work can convince a man to do just about anything""" - approvals = ApprovalService.get_all_approvals(include_cancelled=False) - output = [] - errors = [] - for approval in approvals: - try: - if approval.status != ApprovalStatus.APPROVED.value: - continue - 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 = [] - details.append(pi_details) - for person in personnel: - uid = person['PersonnelComputingID']['value'] - details.append(LdapService.user_info(uid)) + content = ApprovalService.get_not_really_csv_content() - for person in details: - record = { - "study_id": approval.study_id, - "pi_uid": pi_details.uid, - "pi": pi_details.display_name, - "name": person.display_name, - "uid": person.uid, - "email": person.email_address, - "supervisor": "", - "review_complete": review_complete, - } - # We only know the PI's supervisor. - if person.uid == pi_details.uid: - record["supervisor"] = pi_supervisor + return content - output.append(record) - - except Exception as e: - errors.append("Error pulling data for workflow #%i: %s" % (approval.workflow_id, str(e))) - return {"results": output, "errors": errors } - - -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 # ----- come back to the world of the living ---- # diff --git a/crc/services/approval_service.py b/crc/services/approval_service.py index f98733a5..cd3a6549 100644 --- a/crc/services/approval_service.py +++ b/crc/services/approval_service.py @@ -1,3 +1,6 @@ +import json +import pickle +from base64 import b64decode from datetime import datetime, timedelta from sqlalchemy import desc, func @@ -110,19 +113,51 @@ class ApprovalService(object): return [Approval.from_model(approval_model) for approval_model in db_approvals] @staticmethod - def get_health_attesting_records(all_approvals=True): - """Return a list with prepared information related to all approvals - approved or filtered by today """ - if all_approvals: - approvals = session.query(ApprovalModel).filter( - ApprovalModel.status==ApprovalStatus.APPROVED.value - ) - else: - today = datetime.now().date() - approvals = session.query(ApprovalModel).filter( - func.date(ApprovalModel.date_created)==today, - ApprovalModel.status==ApprovalStatus.APPROVED.value - ) + 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} + 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 + + @staticmethod + def get_health_attesting_records(): + """Return a list with prepared information related to all approvals """ + + approvals = ApprovalService.get_all_approvals(include_cancelled=False) health_attesting_rows = [ ['university_computing_id', @@ -132,22 +167,60 @@ class ApprovalService(object): 'job_title', 'supervisor_university_computing_id'] ] + for approval in approvals: - pi_info = LdapService.user_info(approval.study.primary_investigator_id) - approver_info = LdapService.user_info(approval.approver_uid) - first_name = pi_info.given_name - last_name = pi_info.display_name.replace(first_name, '').strip() - health_attesting_rows.append([ - pi_info.uid, - last_name, - first_name, - '', - 'Academic Researcher', - approver_info.uid - ]) + try: + details = ApprovalService.get_approval_details(approval) + if not details: + continue + + for person in details['person_details']: + first_name = person.given_name + last_name = person.display_name.replace(first_name, '').strip() + record = [ + person.uid, + last_name, + first_name, + '', + 'Academic Researcher', + details['Supervisor'] if person.uid == details['person_details'][0].uid else 'askresearch' + ] + + if record not in health_attesting_rows: + health_attesting_rows.append(record) + + except Exception as e: + app.logger.error("Error pulling data for workflow #%i: %s" % (approval.workflow_id, str(e))) return health_attesting_rows + @staticmethod + def get_not_really_csv_content(): + approvals = ApprovalService.get_all_approvals(include_cancelled=False) + output = [] + errors = [] + for approval in approvals: + try: + details = ApprovalService.get_approval_details(approval) + + for person in details['person_details']: + record = { + "study_id": approval.study_id, + "pi_uid": pi_details.uid, + "pi": pi_details.display_name, + "name": person.display_name, + "uid": person.uid, + "email": person.email_address, + "supervisor": details['Supervisor'] if person.uid == details['person_details'][0].uid else "", + "review_complete": review_complete, + } + + output.append(record) + + except Exception as e: + errors.append("Error pulling data for workflow #%i: %s" % (approval.workflow_id, str(e))) + return {"results": output, "errors": errors } + @staticmethod def update_approval(approval_id, approver_uid): """Update a specific approval diff --git a/tests/test_approvals_service.py b/tests/test_approvals_service.py index 26a26ef4..d8f8d503 100644 --- a/tests/test_approvals_service.py +++ b/tests/test_approvals_service.py @@ -57,6 +57,32 @@ class TestApprovalsService(BaseTest): self.assertEqual(1, models[0].version) self.assertEqual(2, models[1].version) + def test_get_health_attesting_records(self): + self.load_example_data() + self.create_reference_document() + workflow = self.create_workflow('empty_workflow') + FileService.add_workflow_file(workflow_id=workflow.id, + name="anything.png", content_type="text", + binary_data=b'5678', irb_doc_code="AD_CoCAppr") + + ApprovalService.add_approval(study_id=workflow.study_id, workflow_id=workflow.id, approver_uid="dhf8r") + records = ApprovalService.get_health_attesting_records() + + self.assertEqual(len(records), 1) + + def test_get_not_really_csv_content(self): + self.load_example_data() + self.create_reference_document() + workflow = self.create_workflow('empty_workflow') + FileService.add_workflow_file(workflow_id=workflow.id, + name="anything.png", content_type="text", + binary_data=b'5678', irb_doc_code="AD_CoCAppr") + + ApprovalService.add_approval(study_id=workflow.study_id, workflow_id=workflow.id, approver_uid="dhf8r") + records = ApprovalService.get_not_really_csv_content() + + self.assertEqual(len(records), 1) + def test_new_approval_sends_proper_emails(self): self.assertEqual(1, 1) diff --git a/tests/test_tasks_api.py b/tests/test_tasks_api.py index 1b35434c..7288b5e4 100644 --- a/tests/test_tasks_api.py +++ b/tests/test_tasks_api.py @@ -47,7 +47,7 @@ class TestTasksApi(BaseTest): # The total number of tasks may change over time, as users move through gateways # branches may be pruned. As we hit parallel Multi-Instance new tasks may be created... self.assertIsNotNone(workflow.total_tasks) - self.assertEquals(prev_completed_task_count + 1, workflow.completed_tasks) + self.assertEqual(prev_completed_task_count + 1, workflow.completed_tasks) # Assure a record exists in the Task Events task_events = session.query(TaskEventModel) \ .filter_by(workflow_id=workflow.id) \ @@ -56,25 +56,25 @@ class TestTasksApi(BaseTest): self.assertGreater(len(task_events), 0) event = task_events[0] self.assertIsNotNone(event.study_id) - self.assertEquals("dhf8r", event.user_uid) - self.assertEquals(workflow.id, event.workflow_id) - self.assertEquals(workflow.workflow_spec_id, event.workflow_spec_id) - self.assertEquals(workflow.spec_version, event.spec_version) - self.assertEquals(WorkflowService.TASK_ACTION_COMPLETE, event.action) - self.assertEquals(task_in.id, task_id) - self.assertEquals(task_in.name, event.task_name) - self.assertEquals(task_in.title, event.task_title) - self.assertEquals(task_in.type, event.task_type) - self.assertEquals("COMPLETED", event.task_state) + self.assertEqual("dhf8r", event.user_uid) + self.assertEqual(workflow.id, event.workflow_id) + self.assertEqual(workflow.workflow_spec_id, event.workflow_spec_id) + self.assertEqual(workflow.spec_version, event.spec_version) + self.assertEqual(WorkflowService.TASK_ACTION_COMPLETE, event.action) + self.assertEqual(task_in.id, task_id) + self.assertEqual(task_in.name, event.task_name) + self.assertEqual(task_in.title, event.task_title) + self.assertEqual(task_in.type, event.task_type) + self.assertEqual("COMPLETED", event.task_state) # Not sure what vodoo is happening inside of marshmallow to get me in this state. if isinstance(task_in.multi_instance_type, MultiInstanceType): - self.assertEquals(task_in.multi_instance_type.value, event.mi_type) + self.assertEqual(task_in.multi_instance_type.value, event.mi_type) else: - self.assertEquals(task_in.multi_instance_type, event.mi_type) + self.assertEqual(task_in.multi_instance_type, event.mi_type) - self.assertEquals(task_in.multi_instance_count, event.mi_count) - self.assertEquals(task_in.multi_instance_index, event.mi_index) - self.assertEquals(task_in.process_name, event.process_name) + self.assertEqual(task_in.multi_instance_count, event.mi_count) + self.assertEqual(task_in.multi_instance_index, event.mi_index) + self.assertEqual(task_in.process_name, event.process_name) self.assertIsNotNone(event.date) # Assure that there is data in the form_data