Merge branch 'dev' of github.com:sartography/cr-connect-workflow into dev

This commit is contained in:
Dan 2021-05-18 09:53:24 -04:00
commit 2caf548719
9 changed files with 234 additions and 36 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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,
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})
'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

View File

@ -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
}

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_07672t3" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.5.0">
<bpmn:process id="Process_StudyInfo" name="Study Info" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_0wpuk5u</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="SequenceFlow_0wpuk5u" sourceRef="StartEvent_1" targetRef="Task_WhichInfo" />
<bpmn:userTask id="Task_WhichInfo" name="Which Info" camunda:formKey="WhichForm">
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="which" label="Which" type="enum">
<camunda:value id="info" name="Info" />
<camunda:value id="investigators" name="Investigators" />
<camunda:value id="roles" name="Roles" />
<camunda:value id="details" name="Details" />
<camunda:value id="documents" name="Documents" />
<camunda:value id="sponsors" name="Sponsors" />
</camunda:formField>
</camunda:formData>
</bpmn:extensionElements>
<bpmn:incoming>SequenceFlow_0wpuk5u</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_0vpsh2s</bpmn:outgoing>
</bpmn:userTask>
<bpmn:sequenceFlow id="SequenceFlow_0vpsh2s" sourceRef="Task_WhichInfo" targetRef="Task_GetInfo" />
<bpmn:scriptTask id="Task_GetInfo" name="Get Info">
<bpmn:incoming>SequenceFlow_0vpsh2s</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_0y0c2i4</bpmn:outgoing>
<bpmn:script>info = study_info(which)</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="SequenceFlow_0y0c2i4" sourceRef="Task_GetInfo" targetRef="Task_PrintInfo" />
<bpmn:manualTask id="Task_PrintInfo" name="Print Info">
<bpmn:documentation># Info
&lt;div&gt;&lt;span&gt;info is {{info}}&lt;/span&gt;&lt;/div&gt;
</bpmn:documentation>
<bpmn:incoming>SequenceFlow_0y0c2i4</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_1s1u06z</bpmn:outgoing>
</bpmn:manualTask>
<bpmn:endEvent id="EndEvent_0681rbo">
<bpmn:incoming>SequenceFlow_1s1u06z</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_1s1u06z" sourceRef="Task_PrintInfo" targetRef="EndEvent_0681rbo" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_StudyInfo">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_0wpuk5u_di" bpmnElement="SequenceFlow_0wpuk5u">
<di:waypoint x="215" y="117" />
<di:waypoint x="270" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="UserTask_1id33on_di" bpmnElement="Task_WhichInfo">
<dc:Bounds x="270" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_0vpsh2s_di" bpmnElement="SequenceFlow_0vpsh2s">
<di:waypoint x="370" y="117" />
<di:waypoint x="430" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="ScriptTask_0vpqnbr_di" bpmnElement="Task_GetInfo">
<dc:Bounds x="430" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_0y0c2i4_di" bpmnElement="SequenceFlow_0y0c2i4">
<di:waypoint x="530" y="117" />
<di:waypoint x="590" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="ManualTask_09vvuig_di" bpmnElement="Task_PrintInfo">
<dc:Bounds x="590" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEvent_0681rbo_di" bpmnElement="EndEvent_0681rbo">
<dc:Bounds x="752" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_1s1u06z_di" bpmnElement="SequenceFlow_1s1u06z">
<di:waypoint x="690" y="117" />
<di:waypoint x="752" y="117" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -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)

View File

@ -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):

View File

@ -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'])

View File

@ -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)