Merge branch 'dev' of github.com:sartography/cr-connect-workflow into dev
This commit is contained in:
commit
2caf548719
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
<div><span>info is {{info}}</span></div>
|
||||
</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>
|
|
@ -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)
|
|
@ -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):
|
||||
|
|
|
@ -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'])
|
|
@ -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)
|
Loading…
Reference in New Issue