diff --git a/crc/api/workflow.py b/crc/api/workflow.py index b51cdb2c..fcba4d26 100644 --- a/crc/api/workflow.py +++ b/crc/api/workflow.py @@ -163,7 +163,7 @@ def get_task_events(action = None, workflow = None, study = None): study = session.query(StudyModel).filter(StudyModel.id == event.study_id).first() workflow = session.query(WorkflowModel).filter(WorkflowModel.id == event.workflow_id).first() workflow_meta = WorkflowMetadata.from_workflow(workflow) - if study.status in [StudyStatus.open_for_enrollment, StudyStatus.in_progress]: + if study and study.status in [StudyStatus.open_for_enrollment, StudyStatus.in_progress]: task_events.append(TaskEvent(event, study, workflow_meta)) return TaskEventSchema(many=True).dump(task_events) diff --git a/crc/scripts/study_info.py b/crc/scripts/study_info.py index a372c604..b5e49e84 100644 --- a/crc/scripts/study_info.py +++ b/crc/scripts/study_info.py @@ -18,7 +18,7 @@ class StudyInfo(Script): """Please see the detailed description that is provided below. """ pb = ProtocolBuilderService() - type_options = ['info', 'investigators', 'roles', 'details', 'documents', 'protocol', 'sponsors'] + type_options = ['info', 'investigators', 'roles', 'details', 'documents', 'sponsors'] # This is used for test/workflow validation, as well as documentation. example_data = { @@ -110,9 +110,6 @@ class StudyInfo(Script): }, "details": {}, - 'protocol': { - id: 0, - } } } @@ -392,8 +389,6 @@ Returns information specific to the protocol. retval = self.pb.get_sponsors(study_id) if cmd == 'documents': retval = StudyService().get_documents_status(study_id) - if cmd == 'protocol': - retval = StudyService().get_protocol(study_id) return self.box_it(retval, prefix) diff --git a/crc/services/study_service.py b/crc/services/study_service.py index b01c42b8..ad5c780d 100644 --- a/crc/services/study_service.py +++ b/crc/services/study_service.py @@ -6,11 +6,13 @@ from typing import List import flask import requests from SpiffWorkflow import WorkflowException +from SpiffWorkflow.bpmn.PythonScriptEngine import Box from SpiffWorkflow.exceptions import WorkflowTaskExecException from ldap3.core.exceptions import LDAPSocketOpenError from crc import db, session, app from crc.api.common import ApiError +from crc.models.data_store import DataStoreModel from crc.models.email import EmailModel from crc.models.file import FileDataModel, FileModel, FileModelSchema, File, LookupFileModel, LookupDataModel from crc.models.ldap import LdapSchema @@ -297,21 +299,27 @@ class StudyService(object): if hasattr(flask.g,'user'): token = flask.g.user.encode_auth_token() for file in doc_files: - doc['files'].append({'file_id': file.id, - 'name': file.name, - 'url': app.config['APPLICATION_ROOT']+ - 'file/' + str(file.id) + - '/download?auth_token='+ - urllib.parse.quote_plus(token), - 'workflow_id': file.workflow_id}) - + file_data = {'file_id': file.id, + 'name': file.name, + 'url': app.config['APPLICATION_ROOT']+ + 'file/' + str(file.id) + + '/download?auth_token='+ + urllib.parse.quote_plus(token), + 'workflow_id': file.workflow_id + } + data = db.session.query(DataStoreModel).filter(DataStoreModel.file_id==file.id).all() + data_store_data = {} + for d in data: + data_store_data[d.key] = d.value + file_data["data_store"] = data_store_data + doc['files'].append(Box(file_data)) # update the document status to match the status of the workflow it is in. if 'status' not in doc or doc['status'] is None: workflow: WorkflowModel = session.query(WorkflowModel).filter_by(id=file.workflow_id).first() doc['status'] = workflow.status.value documents[code] = doc - return documents + return Box(documents) @staticmethod def get_investigators(study_id, all=False): @@ -355,16 +363,6 @@ class StudyService(object): app.logger.info("Failed to connect to LDAP Server.") return {} - @staticmethod - def get_protocol(study_id): - """Returns the study protocol, if it has been uploaded.""" - file = db.session.query(FileModel) \ - .filter_by(study_id=study_id) \ - .filter_by(form_field_key='Study_Protocol_Document') \ - .first() - - return FileModelSchema().dump(file) - @staticmethod def synch_with_protocol_builder_if_enabled(user): """Assures that the studies we have locally for the given user are diff --git a/tests/data/pb_responses/study_details.json b/tests/data/pb_responses/study_details.json index 8699efff..6c74481b 100644 --- a/tests/data/pb_responses/study_details.json +++ b/tests/data/pb_responses/study_details.json @@ -1,12 +1,12 @@ { - "DSMB": null, - "DSMB_FREQUENCY": null, - "GCRC_NUMBER": null, - "IBC_NUMBER": null, - "IDE": null, - "IND_1": 1234, - "IND_2": null, - "IND_3": null, + "DSMB": 1, + "DSMB_FREQUENCY": 2, + "GCRC_NUMBER": "9", + "IBC_NUMBER": "7", + "IDE": "12345", + "IND_1": "1234", + "IND_2": "2345", + "IND_3": "3456", "IRBREVIEWERADMIN": null, "IS_ADULT_PARTICIPANT": null, "IS_APPROVED_DEVICE": null, @@ -61,6 +61,6 @@ "NON_UVA_LOCATION": null, "OTHER_VULNERABLE_DESC": null, "PRC_NUMBER": null, - "SPONSORS_PROTOCOL_REVISION_DATE": null, + "SPONSORS_PROTOCOL_REVISION_DATE": "2021-04-20", "UPLOAD_COMPLETE": null } \ No newline at end of file diff --git a/tests/data/study_info_script/study_info_script.bpmn b/tests/data/study_info_script/study_info_script.bpmn new file mode 100644 index 00000000..9a32791d --- /dev/null +++ b/tests/data/study_info_script/study_info_script.bpmn @@ -0,0 +1,78 @@ + + + + + SequenceFlow_0wpuk5u + + + + + + + + + + + + + + + + SequenceFlow_0wpuk5u + SequenceFlow_0vpsh2s + + + + SequenceFlow_0vpsh2s + SequenceFlow_0y0c2i4 + info = study_info(which) + + + + # Info +<div><span>info is {{info}}</span></div> + + SequenceFlow_0y0c2i4 + SequenceFlow_1s1u06z + + + SequenceFlow_1s1u06z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/study/test_study_details_documents.py b/tests/study/test_study_details_documents.py index ef1180fc..757ff938 100644 --- a/tests/study/test_study_details_documents.py +++ b/tests/study/test_study_details_documents.py @@ -14,6 +14,7 @@ from crc.scripts.study_info import StudyInfo from crc.services.file_service import FileService from crc.services.study_service import StudyService from crc.services.workflow_processor import WorkflowProcessor +from crc.scripts.file_data_set import FileDataSet class TestStudyDetailsDocumentsScript(BaseTest): @@ -93,3 +94,23 @@ class TestStudyDetailsDocumentsScript(BaseTest): docs = StudyInfo().do_task_validate_only(task, study.id, workflow_model.id, "documents") self.assertTrue(isinstance(docs, Box)) + @patch('crc.services.protocol_builder.requests.get') + def test_study_info_returns_document_data_store_values_with_documents(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) + irb_code = "UVACompl_PRCAppr" # The first file referenced in pb required docs. + file = FileService.add_workflow_file(workflow_id=workflow_model.id, + 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="ginger", value="doodle", file_id=file.id) + docs = StudyInfo().do_task(task, study.id, workflow_model.id, "documents") + self.assertTrue(isinstance(docs, Box)) + self.assertEquals(1, len(docs.UVACompl_PRCAppr.files)) + self.assertEquals("doodle", docs.UVACompl_PRCAppr.files[0].data_store.ginger) \ No newline at end of file diff --git a/tests/test_protocol_builder.py b/tests/test_protocol_builder.py index d82d7f0b..8bd1f66f 100644 --- a/tests/test_protocol_builder.py +++ b/tests/test_protocol_builder.py @@ -47,7 +47,7 @@ class TestProtocolBuilder(BaseTest): response = ProtocolBuilderService.get_study_details(self.test_study_id) self.assertIsNotNone(response) self.assertEqual(64, len(response)) - self.assertEqual(1234, response['IND_1']) + self.assertEqual('1234', response['IND_1']) @patch('crc.services.protocol_builder.requests.get') def test_get_study_sponsors(self, mock_get): diff --git a/tests/test_study_info_script.py b/tests/test_study_info_script.py new file mode 100644 index 00000000..18ac77a9 --- /dev/null +++ b/tests/test_study_info_script.py @@ -0,0 +1,79 @@ +from tests.base_test import BaseTest +from crc.scripts.study_info import StudyInfo +from crc import app +from unittest.mock import patch +from crc.services.protocol_builder import ProtocolBuilderService + + +class TestStudyInfoScript(BaseTest): + + test_uid = "dhf8r" + test_study_id = 1 + + def do_work(self, info_type): + app.config['PB_ENABLED'] = True + self.load_example_data() + workflow = self.create_workflow('study_info_script') + workflow_api = self.get_workflow_api(workflow) + # grab study_info directly from script + study_info = StudyInfo().do_task(workflow_api.study_id, workflow.study.id, workflow.id, info_type) + + # grab study info through a workflow + first_task = workflow_api.next_task + self.complete_form(workflow, first_task, {'which': info_type}) + workflow_api = self.get_workflow_api(workflow) + second_task = workflow_api.next_task + + return study_info, second_task + + def test_info_script_info(self): + study_info, second_task = self.do_work(info_type='info') + + self.assertEqual(study_info['title'], second_task.data['info']['title']) + self.assertEqual(study_info['primary_investigator_id'], second_task.data['info']['primary_investigator_id']) + self.assertIn(study_info['title'], second_task.documentation) + + @patch('crc.services.protocol_builder.requests.get') + def test_info_script_investigators(self, mock_get): + app.config['PB_ENABLED'] = True + mock_get.return_value.ok = True + mock_get.return_value.text = self.protocol_builder_response('investigators.json') + response = ProtocolBuilderService.get_investigators(self.test_study_id) + study_info, second_task = self.do_work(info_type='investigators') + for i in range(len(response)): + r = response[i] + s = second_task.data['info'][response[i]['INVESTIGATORTYPE']] + self.assertEqual(r['INVESTIGATORTYPEFULL'], s['label']) + + # def test_info_script_roles(self): + # study_info, second_task = self.do_work(info_type='roles') + # self.assertEqual(study_info, second_task.data['info']) + + @patch('crc.services.protocol_builder.requests.get') + def test_info_script_details(self, mock_get): + app.config['PB_ENABLED'] = True + mock_get.return_value.ok = True + mock_get.return_value.text = self.protocol_builder_response('study_details.json') + response = ProtocolBuilderService.get_study_details(self.test_study_id) + study_info, second_task = self.do_work(info_type='details') + self.assertEqual(response['IBC_NUMBER'], second_task.data['info']['IBC_NUMBER']) + self.assertEqual(response['IDE'], second_task.data['info']['IDE']) + self.assertEqual(response['IND_1'], second_task.data['info']['IND_1']) + self.assertEqual(response['IND_2'], second_task.data['info']['IND_2']) + self.assertEqual(response['IND_3'], second_task.data['info']['IND_3']) + + # def test_info_script_documents(self): + # study_info, second_task = self.do_work(info_type='documents') + # self.assertEqual(study_info, second_task.data['info']) + + @patch('crc.services.protocol_builder.requests.get') + def test_info_script_sponsors(self, mock_get): + app.config['PB_ENABLED'] = True + mock_get.return_value.ok = True + mock_get.return_value.text = self.protocol_builder_response('sponsors.json') + response = ProtocolBuilderService.get_sponsors(self.test_study_id) + study_info, second_task = self.do_work(info_type='sponsors') + for i in range(len(response)): + self.assertEqual(response[i]['SPONSOR_ID'], second_task.data['info'][i]['SPONSOR_ID']) + self.assertEqual(response[i]['SP_NAME'], second_task.data['info'][i]['SP_NAME']) + self.assertEqual(response[i]['SS_STUDY'], second_task.data['info'][i]['SS_STUDY']) diff --git a/tests/test_workflow_api.py b/tests/test_workflow_api.py new file mode 100644 index 00000000..02b5b348 --- /dev/null +++ b/tests/test_workflow_api.py @@ -0,0 +1,27 @@ +from tests.base_test import BaseTest + +from crc import session +from crc.models.user import UserModel +from crc.services.user_service import UserService +from crc.services.workflow_service import WorkflowService + +from example_data import ExampleDataLoader + +import json + + +class TestWorkflowApi(BaseTest): + + def test_get_task_events(self): + + self.load_example_data() + spec = ExampleDataLoader().create_spec('hello_world', 'Hello World', category_id=0, standalone=True, from_tests=True) + user = session.query(UserModel).first() + self.assertIsNotNone(user) + WorkflowService.get_workflow_from_spec(spec.id, user) + + rv = self.app.get(f'/v1.0/task_events', + follow_redirects=True, + content_type="application/json", + headers=self.logged_in_headers()) + self.assert_success(rv)