From a5cef8775e23c4092ca70f57147b585aab2a2364 Mon Sep 17 00:00:00 2001 From: Dan Funk Date: Mon, 6 Jul 2020 12:09:21 -0400 Subject: [PATCH] * Modifying the StudyInfo script to return both "investigators" and "roles", the investigators argument will return only the current active investigators according to the protocol builder. The "roles" argument returns all possible roles - it is what "investigators" use to be. * The Task.title returned to the front end will now attempt to process the "display_name" property for dot-notation syntax, making it possible to use this for multi-instance tasks, but will work in all cases where we want he title to change based on values in the data model. * Fixing a bug the test_study_api where it wasn't updated when we made recent changes to the different states of a study. * --- crc/scripts/study_info.py | 21 +++++++++++++++-- crc/services/study_service.py | 4 +++- crc/services/workflow_service.py | 8 +++++-- .../multi_instance_parallel.bpmn | 3 +++ tests/study/test_study_api.py | 4 ++-- tests/study/test_study_service.py | 23 +++++++++++++++++-- tests/test_tasks_api.py | 18 ++++++++++----- .../test_workflow_processor_multi_instance.py | 5 ++++ 8 files changed, 71 insertions(+), 15 deletions(-) diff --git a/crc/scripts/study_info.py b/crc/scripts/study_info.py index e336685d..94e35249 100644 --- a/crc/scripts/study_info.py +++ b/crc/scripts/study_info.py @@ -14,7 +14,7 @@ class StudyInfo(Script): """Please see the detailed description that is provided below. """ pb = ProtocolBuilderService() - type_options = ['info', 'investigators', 'details', 'approvals', 'documents', 'protocol'] + type_options = ['info', 'investigators', 'roles', 'details', 'approvals', 'documents', 'protocol'] # This is used for test/workflow validation, as well as documentation. example_data = { @@ -106,11 +106,20 @@ Returns the basic information such as the id and title ### Investigators ### Returns detailed information about related personnel. The order returned is guaranteed to match the order provided in the investigators.xslx reference file. -If possible, detailed information is added in from LDAP about each personnel based on their user_id. +Detailed information is added in from LDAP about each personnel based on their user_id. ``` {investigators_example} ``` +### Investigator Roles ### +Returns a list of all investigator roles, populating any roles with additional information available from +the Protocol Builder and LDAP. Its basically just like Investigators, but it includes all the roles, rather +that just those that were set in Protocol Builder. +``` +{investigators_example} +``` + + ### Details ### Returns detailed information about variable keys read in from the Protocol Builder. @@ -161,6 +170,12 @@ Returns information specific to the protocol. "INVESTIGATORTYPEFULL": "Primary Investigator", "NETBADGEID": "dhf8r" }, + "roles": + { + "INVESTIGATORTYPE": "PI", + "INVESTIGATORTYPEFULL": "Primary Investigator", + "NETBADGEID": "dhf8r" + }, "details": { "IS_IND": 0, @@ -198,6 +213,8 @@ Returns information specific to the protocol. self.add_data_to_task(task, {cmd: schema.dump(study)}) if cmd == 'investigators': self.add_data_to_task(task, {cmd: StudyService().get_investigators(study_id)}) + if cmd == 'roles': + self.add_data_to_task(task, {cmd: StudyService().get_investigators(study_id, all=True)}) if cmd == 'details': self.add_data_to_task(task, {cmd: self.pb.get_study_details(study_id)}) if cmd == 'approvals': diff --git a/crc/services/study_service.py b/crc/services/study_service.py index 142d6166..1f7429cb 100644 --- a/crc/services/study_service.py +++ b/crc/services/study_service.py @@ -182,7 +182,7 @@ class StudyService(object): return documents @staticmethod - def get_investigators(study_id): + def get_investigators(study_id, all=False): # Loop through all known investigator types as set in the reference file inv_dictionary = FileService.get_reference_data(FileService.INVESTIGATOR_LIST, 'code') @@ -199,6 +199,8 @@ class StudyService(object): else: inv_dictionary[i_type]['user_id'] = None + if not all: + inv_dictionary = dict(filter(lambda elem: elem[1]['user_id'] is not None, inv_dictionary.items())) return inv_dictionary @staticmethod diff --git a/crc/services/workflow_service.py b/crc/services/workflow_service.py index 0faf3b76..09368610 100644 --- a/crc/services/workflow_service.py +++ b/crc/services/workflow_service.py @@ -207,8 +207,10 @@ class WorkflowService(object): if spiff_task: nav_item['task'] = WorkflowService.spiff_task_to_api_task(spiff_task, add_docs_and_forms=False) nav_item['title'] = nav_item['task'].title # Prefer the task title. + else: nav_item['task'] = None + if not 'is_decision' in nav_item: nav_item['is_decision'] = False @@ -333,10 +335,12 @@ class WorkflowService(object): # otherwise strip off the first word of the task, as that should be following # a BPMN standard, and should not be included in the display. if task.properties and "display_name" in task.properties: - task.title = task.properties['display_name'] + try: + task.title = spiff_task.workflow.script_engine.evaluate_expression(spiff_task, task.properties['display_name']) + except Exception as e: + app.logger.info("Failed to set title on task due to type error." + str(e)) elif task.title and ' ' in task.title: task.title = task.title.partition(' ')[2] - return task @staticmethod diff --git a/tests/data/multi_instance_parallel/multi_instance_parallel.bpmn b/tests/data/multi_instance_parallel/multi_instance_parallel.bpmn index dd6215ed..9e53323f 100644 --- a/tests/data/multi_instance_parallel/multi_instance_parallel.bpmn +++ b/tests/data/multi_instance_parallel/multi_instance_parallel.bpmn @@ -17,6 +17,9 @@ + + + SequenceFlow_1p568pp Flow_0ugjw69 diff --git a/tests/study/test_study_api.py b/tests/study/test_study_api.py index cdae21c5..fdf64239 100644 --- a/tests/study/test_study_api.py +++ b/tests/study/test_study_api.py @@ -182,8 +182,8 @@ 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, 1) - self.assertEqual(num_incomplete, 1) + self.assertEqual(num_active, 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) diff --git a/tests/study/test_study_service.py b/tests/study/test_study_service.py index 1c482bcb..1eb020fa 100644 --- a/tests/study/test_study_service.py +++ b/tests/study/test_study_service.py @@ -183,7 +183,7 @@ class TestStudyService(BaseTest): @patch('crc.services.protocol_builder.ProtocolBuilderService.get_investigators') # mock_docs - def test_get_personnel(self, mock_docs): + def test_get_personnel_roles(self, mock_docs): self.load_example_data() # mock out the protocol builder @@ -191,7 +191,7 @@ class TestStudyService(BaseTest): 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) + investigators = StudyService().get_investigators(workflow.study_id, all=True) self.assertEqual(9, len(investigators)) @@ -207,3 +207,22 @@ class TestStudyService(BaseTest): # No value is provided for Department Chair self.assertIsNone(investigators['DEPT_CH']['user_id']) + + @patch('crc.services.protocol_builder.ProtocolBuilderService.get_investigators') # mock_docs + 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(3, 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. diff --git a/tests/test_tasks_api.py b/tests/test_tasks_api.py index 236defdc..abbf8707 100644 --- a/tests/test_tasks_api.py +++ b/tests/test_tasks_api.py @@ -322,7 +322,7 @@ class TestTasksApi(BaseTest): self.assertEqual(4, len(navigation)) # Start task, form_task, multi_task, end task self.assertEqual("UserTask", workflow.next_task.type) self.assertEqual(MultiInstanceType.sequential.value, workflow.next_task.multi_instance_type) - self.assertEqual(9, workflow.next_task.multi_instance_count) + self.assertEqual(3, workflow.next_task.multi_instance_count) # Assure that the names for each task are properly updated, so they aren't all the same. self.assertEqual("Primary Investigator", workflow.next_task.properties['display_name']) @@ -480,17 +480,23 @@ class TestTasksApi(BaseTest): workflow = self.create_workflow('multi_instance_parallel') workflow_api = self.get_workflow_api(workflow) - self.assertEqual(12, len(workflow_api.navigation)) + self.assertEqual(6, len(workflow_api.navigation)) ready_items = [nav for nav in workflow_api.navigation if nav['state'] == "READY"] - self.assertEqual(9, len(ready_items)) + self.assertEqual(3, len(ready_items)) self.assertEqual("UserTask", workflow_api.next_task.type) self.assertEqual("MultiInstanceTask",workflow_api.next_task.name) - self.assertEqual("more information", workflow_api.next_task.title) + self.assertEqual("Primary Investigator", workflow_api.next_task.title) - for i in random.sample(range(9), 9): + for i in random.sample(range(3), 3): task = TaskSchema().load(ready_items[i]['task']) - data = workflow_api.next_task.data + rv = self.app.put('/v1.0/workflow/%i/task/%s/set_token' % (workflow.id, task.id), + headers=self.logged_in_headers(), + content_type="application/json") + self.assert_success(rv) + json_data = json.loads(rv.get_data(as_text=True)) + workflow = WorkflowApiSchema().load(json_data) + data = workflow.next_task.data data['investigator']['email'] = "dhf8r@virginia.edu" self.complete_form(workflow, task, data) #tasks = self.get_workflow_api(workflow).user_tasks diff --git a/tests/workflow/test_workflow_processor_multi_instance.py b/tests/workflow/test_workflow_processor_multi_instance.py index 76821fed..e1011223 100644 --- a/tests/workflow/test_workflow_processor_multi_instance.py +++ b/tests/workflow/test_workflow_processor_multi_instance.py @@ -146,6 +146,11 @@ class TestWorkflowProcessorMultiInstance(BaseTest): api_task = WorkflowService.spiff_task_to_api_task(task) self.assertEqual(MultiInstanceType.parallel, api_task.multi_instance_type) + + # Assure navigation picks up the label of the current element variable. + nav = WorkflowService.processor_to_workflow_api(processor, task).navigation + self.assertEquals("Primary Investigator", nav[2].title) + task.update_data({"investigator": {"email": "dhf8r@virginia.edu"}}) processor.complete_task(task) processor.do_engine_steps()