mirror of
https://github.com/sartography/cr-connect-workflow.git
synced 2025-02-20 11:48:16 +00:00
Standardizing the script tasks that can be executed on the server, adding tons of error messages for when things go wrong. All scripts must exist in side of the crc/scripts directory.
Adding a new script that script tasks can use to add in data about the study. Moving all the test workflow specifications out of the main load. fixing a pile of tests so they can find workflow specs that are now moved into the test directory.
This commit is contained in:
parent
305118e90e
commit
7194d7d374
@ -6,6 +6,7 @@ class ApiError(Exception):
|
||||
self.status_code = status_code
|
||||
self.code = code
|
||||
self.message = message
|
||||
Exception.__init__(self, self.message)
|
||||
|
||||
|
||||
class ApiErrorSchema(ma.Schema):
|
||||
|
@ -10,7 +10,7 @@ from flask import send_file
|
||||
from jinja2 import Template, UndefinedError
|
||||
|
||||
from crc.api.common import ApiError, ApiErrorSchema
|
||||
from crc.scripts.CompleteTemplate import CompleteTemplate
|
||||
from crc.scripts.complete_template import CompleteTemplate
|
||||
from crc.services.file_service import FileService
|
||||
|
||||
|
||||
|
@ -1,24 +0,0 @@
|
||||
import requests
|
||||
|
||||
|
||||
class LoadStudies:
|
||||
"""Just your basic class that can pull in data from a few api endpoints and do a basic task."""
|
||||
|
||||
def do_task(self, task_data):
|
||||
print('*** LoadStudies > do_task ***')
|
||||
print('task_data', task_data)
|
||||
|
||||
class LoadStudy:
|
||||
"""Just your basic class that can pull in data from a few api endpoints and do a basic task."""
|
||||
|
||||
def do_task(self, task_data):
|
||||
print('*** LoadStudies > do_task ***')
|
||||
print('task_data', task_data)
|
||||
irb_study = {
|
||||
'tbd': 0,
|
||||
'protocol_builder_available': True,
|
||||
'irb_review_type': 'Full Board',
|
||||
'irb_requires': True,
|
||||
}
|
||||
|
||||
return irb_study
|
@ -9,17 +9,21 @@ from crc.models.workflow import WorkflowSpecModel
|
||||
from docxtpl import DocxTemplate
|
||||
import jinja2
|
||||
|
||||
from crc.scripts.script import Script
|
||||
from crc.services.file_service import FileService
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
|
||||
|
||||
class CompleteTemplate(object):
|
||||
class CompleteTemplate(Script):
|
||||
|
||||
def get_description(self):
|
||||
return
|
||||
"""Completes a word template, using the data available in the current task. Heavy on the
|
||||
error messages, because there is so much that can go wrong here, and we want to provide
|
||||
as much feedback as possible. Some of this might move up to a higher level object or be
|
||||
passed into all tasks as we complete more work."""
|
||||
|
||||
def do_task(self, task, *args, **kwargs):
|
||||
def do_task(self, task, study_id, *args, **kwargs):
|
||||
"""Entry point, mostly worried about wiring it all up."""
|
||||
if len(args) != 1:
|
||||
raise ApiError(code="missing_argument",
|
@ -1,8 +1,11 @@
|
||||
import requests
|
||||
|
||||
from crc.scripts.script import Script
|
||||
|
||||
class FactService:
|
||||
"""Just your basic class that can pull in data from a few api endpoints and do a basic task."""
|
||||
|
||||
class FactService(Script):
|
||||
def get_description(self):
|
||||
return """Just your basic class that can pull in data from a few api endpoints and do a basic task."""
|
||||
|
||||
def get_cat(self):
|
||||
response = requests.get('https://cat-fact.herokuapp.com/facts/random')
|
||||
@ -16,7 +19,7 @@ class FactService:
|
||||
response = requests.get('https://api.chucknorris.io/jokes/random')
|
||||
return response.json()['value']
|
||||
|
||||
def do_task(self, task, **kwargs):
|
||||
def do_task(self, task, study_id, **kwargs):
|
||||
print(task.data)
|
||||
|
||||
if "type" not in task.data:
|
15
crc/scripts/script.py
Normal file
15
crc/scripts/script.py
Normal file
@ -0,0 +1,15 @@
|
||||
from crc.api.common import ApiError
|
||||
|
||||
|
||||
class Script:
|
||||
""" Provides an abstract class that defines how scripts should work, this
|
||||
must be extended in all Script Tasks."""
|
||||
|
||||
def get_description(self):
|
||||
raise ApiError("invalid_script",
|
||||
"This script does not supply a description.")
|
||||
|
||||
def do_task(self, task, study_id, **kwargs):
|
||||
raise ApiError("invalid_script",
|
||||
"This is an internal error. The script you are trying to execute " +
|
||||
"does not properly implement the do_task function.")
|
46
crc/scripts/study_info.py
Normal file
46
crc/scripts/study_info.py
Normal file
@ -0,0 +1,46 @@
|
||||
import requests
|
||||
|
||||
from crc import session
|
||||
from crc.api.common import ApiError
|
||||
from crc.models.study import StudyModel, StudyModelSchema
|
||||
from crc.scripts.script import Script
|
||||
from crc.services.protocol_builder import ProtocolBuilderService
|
||||
|
||||
|
||||
class StudyInfo(Script):
|
||||
"""Just your basic class that can pull in data from a few api endpoints and do a basic task."""
|
||||
pb = ProtocolBuilderService()
|
||||
type_options = ['info', 'investigators', 'required_docs', 'details']
|
||||
|
||||
def get_description(self):
|
||||
return """
|
||||
StudyInfo [TYPE] is one of 'info', 'investigators','required_docs', 'details'
|
||||
Adds details about the current study to the Task Data. The type of information required should be
|
||||
provided as an argument. Basic returns the basic information such as the title. Investigators provides
|
||||
detailed information about each investigator in th study. Details provides a large number
|
||||
of details about the study, as gathered within the protocol builder, and 'required_docs',
|
||||
lists all the documents the Protocol Builder has determined will be required as a part of
|
||||
this study.
|
||||
"""
|
||||
|
||||
def do_task(self, task, study_id, *args, **kwargs):
|
||||
if len(args) != 1 or (args[0] not in StudyInfo.type_options):
|
||||
raise ApiError(code="missing_argument",
|
||||
message="The StudyInfo script requires a single argument which must be "
|
||||
"one of %s" % ",".join(StudyInfo.type_options))
|
||||
cmd = args[0]
|
||||
if cmd == 'info':
|
||||
study = session.query(StudyModel).filter_by(id=study_id).first()
|
||||
schema = StudyModelSchema()
|
||||
details = {"study": {"info": schema.dump(study)}}
|
||||
task.data.update(details)
|
||||
if cmd == 'investigators':
|
||||
details = {"study": {"investigators": self.pb.get_investigators(study_id)}}
|
||||
task.data.update(details)
|
||||
if cmd == 'required_docs':
|
||||
details = {"study": {"required_docs": self.pb.get_required_docs(study_id)}}
|
||||
task.data.update(details)
|
||||
if cmd == 'details':
|
||||
details = {"study": {"details": self.pb.get_study_details(study_id)}}
|
||||
task.data.update(details)
|
||||
|
@ -4,6 +4,7 @@ from typing import List, Optional
|
||||
import requests
|
||||
|
||||
from crc import app
|
||||
from crc.api.common import ApiError
|
||||
from crc.models.protocol_builder import ProtocolBuilderStudy, ProtocolBuilderStudySchema, ProtocolBuilderInvestigator, \
|
||||
ProtocolBuilderRequiredDocument, ProtocolBuilderStudyDetails, ProtocolBuilderInvestigatorSchema, \
|
||||
ProtocolBuilderRequiredDocumentSchema, ProtocolBuilderStudyDetailsSchema
|
||||
@ -17,36 +18,54 @@ class ProtocolBuilderService(object):
|
||||
|
||||
@staticmethod
|
||||
def get_studies(user_id) -> Optional[List[ProtocolBuilderStudy]]:
|
||||
if not isinstance(user_id, str):
|
||||
raise ApiError("invalid_user_id", "This user id is invalid: " + str(user_id))
|
||||
response = requests.get(ProtocolBuilderService.STUDY_URL % user_id)
|
||||
if response.ok and response.text:
|
||||
pb_studies = ProtocolBuilderStudySchema(many=True).loads(response.text)
|
||||
return pb_studies
|
||||
else:
|
||||
return None
|
||||
raise ApiError("protocol_builder_error",
|
||||
"Received an invalid response from the protocol builder (status %s): %s" %
|
||||
(response.status_code, response.text))
|
||||
|
||||
@staticmethod
|
||||
def get_investigators(study_id) -> Optional[List[ProtocolBuilderInvestigator]]:
|
||||
ProtocolBuilderService.check_args(study_id)
|
||||
response = requests.get(ProtocolBuilderService.INVESTIGATOR_URL % study_id)
|
||||
if response.ok and response.text:
|
||||
pb_studies = ProtocolBuilderInvestigatorSchema(many=True).loads(response.text)
|
||||
return pb_studies
|
||||
else:
|
||||
return None
|
||||
raise ApiError("protocol_builder_error",
|
||||
"Received an invalid response from the protocol builder (status %s): %s" %
|
||||
(response.status_code, response.text))
|
||||
|
||||
@staticmethod
|
||||
def get_required_docs(study_id) -> Optional[List[ProtocolBuilderRequiredDocument]]:
|
||||
ProtocolBuilderService.check_args(study_id)
|
||||
response = requests.get(ProtocolBuilderService.REQUIRED_DOCS_URL % study_id)
|
||||
if response.ok and response.text:
|
||||
pb_studies = ProtocolBuilderRequiredDocumentSchema(many=True).loads(response.text)
|
||||
return pb_studies
|
||||
else:
|
||||
return None
|
||||
raise ApiError("protocol_builder_error",
|
||||
"Received an invalid response from the protocol builder (status %s): %s" %
|
||||
(response.status_code, response.text))
|
||||
|
||||
@staticmethod
|
||||
def get_study_details(study_id) -> Optional[ProtocolBuilderStudyDetails]:
|
||||
ProtocolBuilderService.check_args(study_id)
|
||||
response = requests.get(ProtocolBuilderService.STUDY_DETAILS_URL % study_id)
|
||||
if response.ok and response.text:
|
||||
pb_study_details = ProtocolBuilderStudyDetailsSchema().loads(response.text)
|
||||
return pb_study_details
|
||||
else:
|
||||
return None
|
||||
raise ApiError("protocol_builder_error",
|
||||
"Received an invalid response from the protocol builder (status %s): %s" %
|
||||
(response.status_code, response.text))
|
||||
|
||||
@staticmethod
|
||||
def check_args(study_id):
|
||||
if not isinstance(study_id, int):
|
||||
raise ApiError("invalid_study_id", "This study id is invalid: " + str(study_id))
|
||||
|
@ -1,3 +1,4 @@
|
||||
import re
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
|
||||
from SpiffWorkflow import Task as SpiffTask, Workflow
|
||||
@ -13,6 +14,7 @@ from crc import session
|
||||
from crc.api.common import ApiError
|
||||
from crc.models.file import FileDataModel, FileModel, FileType
|
||||
from crc.models.workflow import WorkflowStatus, WorkflowModel
|
||||
from crc.scripts.script import Script
|
||||
|
||||
|
||||
class CustomBpmnScriptEngine(BpmnScriptEngine):
|
||||
@ -20,7 +22,7 @@ class CustomBpmnScriptEngine(BpmnScriptEngine):
|
||||
Rather than execute arbitrary code, this assumes the script references a fully qualified python class
|
||||
such as myapp.RandomFact. """
|
||||
|
||||
def execute(self, task, script, **kwargs):
|
||||
def execute(self, task:SpiffTask, script, **kwargs):
|
||||
"""
|
||||
Assume that the script read in from the BPMN file is a fully qualified python class. Instantiate
|
||||
that class, pass in any data available to the current task so that it might act on it.
|
||||
@ -29,11 +31,31 @@ class CustomBpmnScriptEngine(BpmnScriptEngine):
|
||||
This allows us to reference custom code from the BPMN diagram.
|
||||
"""
|
||||
commands = script.split(" ")
|
||||
module_name = "crc." + commands[0]
|
||||
class_name = module_name.split(".")[-1]
|
||||
mod = __import__(module_name, fromlist=[class_name])
|
||||
klass = getattr(mod, class_name)
|
||||
klass().do_task(task, *commands[1:])
|
||||
path_and_command = commands[0].rsplit(".", 1)
|
||||
if len(path_and_command) == 1:
|
||||
module_name = "crc.scripts." + self.camel_to_snake(path_and_command[0])
|
||||
class_name = path_and_command[0]
|
||||
else:
|
||||
module_name = "crc.scripts." + path_and_command[0] + "." + self.camel_to_snake(path_and_command[1])
|
||||
class_name = path_and_command[1]
|
||||
try:
|
||||
mod = __import__(module_name, fromlist=[class_name])
|
||||
klass = getattr(mod, class_name)
|
||||
study_id = task.workflow.data[WorkflowProcessor.STUDY_ID_KEY]
|
||||
if not isinstance(klass(), Script):
|
||||
raise ApiError("invalid_script",
|
||||
"This is an internal error. The script '%s:%s' you called "
|
||||
"does not properly implement the CRC Script class." %
|
||||
(module_name, class_name))
|
||||
klass().do_task(task, study_id, *commands[1:])
|
||||
except ModuleNotFoundError as mnfe:
|
||||
raise ApiError("invalid_script",
|
||||
"Unable to locate Script: '%s:%s'" % (module_name, class_name), 400)
|
||||
|
||||
@staticmethod
|
||||
def camel_to_snake(camel):
|
||||
camel = camel.strip()
|
||||
return re.sub(r'(?<!^)(?=[A-Z])', '_', camel).lower()
|
||||
|
||||
def evaluate(self, task, expression):
|
||||
"""
|
||||
@ -66,7 +88,7 @@ class WorkflowProcessor(object):
|
||||
_script_engine = CustomBpmnScriptEngine()
|
||||
_serializer = BpmnSerializer()
|
||||
WORKFLOW_ID_KEY = "workflow_id"
|
||||
STUDY_ID_KEY = "session_id"
|
||||
STUDY_ID_KEY = "study_id"
|
||||
|
||||
def __init__(self, workflow_spec_id, bpmn_json):
|
||||
wf_spec = self.get_spec(workflow_spec_id)
|
||||
@ -104,6 +126,7 @@ class WorkflowProcessor(object):
|
||||
def create(cls, study_id, workflow_spec_id):
|
||||
spec = WorkflowProcessor.get_spec(workflow_spec_id)
|
||||
bpmn_workflow = BpmnWorkflow(spec, script_engine=cls._script_engine)
|
||||
bpmn_workflow.data[WorkflowProcessor.STUDY_ID_KEY] = study_id
|
||||
bpmn_workflow.do_engine_steps()
|
||||
json = cls._serializer.serialize_workflow(bpmn_workflow)
|
||||
processor = cls(workflow_spec_id, json)
|
||||
@ -116,7 +139,6 @@ class WorkflowProcessor(object):
|
||||
# a second time to store the serilaization so we can maintain this link within
|
||||
# the spiff-workflow process.
|
||||
processor.bpmn_workflow.data[WorkflowProcessor.WORKFLOW_ID_KEY] = workflow_model.id
|
||||
processor.bpmn_workflow.data[WorkflowProcessor.STUDY_ID_KEY] = study_id
|
||||
workflow_model.bpmn_workflow_json = processor.serialize()
|
||||
session.add(workflow_model)
|
||||
session.commit()
|
||||
|
Binary file not shown.
@ -1,65 +0,0 @@
|
||||
<?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_96a17d9" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.4.1">
|
||||
<bpmn:process id="Process_93a29b3" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>SequenceFlow_0637d8i</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_0637d8i" sourceRef="StartEvent_1" targetRef="task_gather_information" />
|
||||
<bpmn:userTask id="task_gather_information" name="Gather Information" camunda:formKey="example_document_form">
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="full_name" label="What is your name?" type="string" />
|
||||
<camunda:formField id="date" label="date" type="string" />
|
||||
<camunda:formField id="title" label="Title" type="string" />
|
||||
<camunda:formField id="company" label="Company" type="string" />
|
||||
<camunda:formField id="last_name" label="Last Name" type="string" />
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>SequenceFlow_0637d8i</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_1i7hk1a</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1i7hk1a" sourceRef="task_gather_information" targetRef="task_generate_document" />
|
||||
<bpmn:scriptTask id="task_generate_document" name="Generate Document">
|
||||
<bpmn:extensionElements>
|
||||
<camunda:properties>
|
||||
<camunda:property name="template" />
|
||||
</camunda:properties>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>SequenceFlow_1i7hk1a</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_11c35oq</bpmn:outgoing>
|
||||
<bpmn:script>scripts.CompleteTemplate Letter.docx</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:endEvent id="EndEvent_0evb22x">
|
||||
<bpmn:incoming>SequenceFlow_11c35oq</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_11c35oq" sourceRef="task_generate_document" targetRef="EndEvent_0evb22x" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_93a29b3">
|
||||
<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_0637d8i_di" bpmnElement="SequenceFlow_0637d8i">
|
||||
<di:waypoint x="215" y="117" />
|
||||
<di:waypoint x="265" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="UserTask_02o51o8_di" bpmnElement="task_gather_information">
|
||||
<dc:Bounds x="265" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1i7hk1a_di" bpmnElement="SequenceFlow_1i7hk1a">
|
||||
<di:waypoint x="365" y="117" />
|
||||
<di:waypoint x="465" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="ScriptTask_0xjh8x4_di" bpmnElement="task_generate_document">
|
||||
<dc:Bounds x="465" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="EndEvent_0evb22x_di" bpmnElement="EndEvent_0evb22x">
|
||||
<dc:Bounds x="665" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_11c35oq_di" bpmnElement="SequenceFlow_11c35oq">
|
||||
<di:waypoint x="565" y="117" />
|
||||
<di:waypoint x="665" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
@ -55,15 +55,15 @@ class ExampleDataLoader:
|
||||
display_name="CR Connect2 - Training Session - Core Info",
|
||||
description='Part of Milestone 3 Deliverable')
|
||||
workflow_specifications += \
|
||||
self.create_spec(id="crc2_training_session_data_security_plan",
|
||||
name="crc2_training_session_data_security_plan",
|
||||
display_name="CR Connect2 - Training Session - Data Security Plan",
|
||||
description='Part of Milestone 3 Deliverable')
|
||||
self.create_spec(id="crc2_training_session_data_security_plan",
|
||||
name="crc2_training_session_data_security_plan",
|
||||
display_name="CR Connect2 - Training Session - Data Security Plan",
|
||||
description='Part of Milestone 3 Deliverable')
|
||||
workflow_specifications += \
|
||||
self.create_spec(id="sponsor_funding_source",
|
||||
name="sponsor_funding_source",
|
||||
display_name="Sponsor and/or Funding Source ",
|
||||
description='TBD')
|
||||
self.create_spec(id="sponsor_funding_source",
|
||||
name="sponsor_funding_source",
|
||||
display_name="Sponsor and/or Funding Source ",
|
||||
description='TBD')
|
||||
# workflow_specifications += \
|
||||
# self.create_spec(id="m2_demo",
|
||||
# name="m2_demo",
|
||||
@ -74,37 +74,6 @@ class ExampleDataLoader:
|
||||
# name="crc_study_workflow",
|
||||
# display_name="CR Connect Study Workflow",
|
||||
# description='Draft workflow for CR Connect studies.')
|
||||
workflow_specifications += \
|
||||
self.create_spec(id="random_fact",
|
||||
name="random_fact",
|
||||
display_name="Random Fact Generator",
|
||||
description='Displays a random fact about a topic of your choosing.')
|
||||
workflow_specifications += \
|
||||
self.create_spec(id="two_forms",
|
||||
name="two_forms",
|
||||
display_name="Two dump questions on two separate tasks",
|
||||
description='the name says it all')
|
||||
workflow_specifications += \
|
||||
self.create_spec(id="decision_table",
|
||||
name="decision_table",
|
||||
display_name="Form with Decision Table",
|
||||
description='the name says it all')
|
||||
workflow_specifications += \
|
||||
self.create_spec(id="parallel_tasks",
|
||||
name="parallel_tasks",
|
||||
display_name="Parallel tasks",
|
||||
description='Four tasks that can happen simultaneously')
|
||||
workflow_specifications += \
|
||||
self.create_spec(id="exclusive_gateway",
|
||||
name="exclusive_gateway",
|
||||
display_name="Exclusive Gateway Example",
|
||||
description='How to take different paths based on input.')
|
||||
workflow_specifications += \
|
||||
self.create_spec(id="docx",
|
||||
name="docx",
|
||||
display_name="Form with document generation",
|
||||
description='the name says it all')
|
||||
|
||||
all_data = users + studies + workflow_specifications
|
||||
return all_data
|
||||
|
||||
|
@ -27,7 +27,7 @@
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>SequenceFlow_1i7hk1a</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_11c35oq</bpmn:outgoing>
|
||||
<bpmn:script>scripts.CompleteTemplate Letter.docx</bpmn:script>
|
||||
<bpmn:script>CompleteTemplate Letter.docx</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:endEvent id="EndEvent_0evb22x">
|
||||
<bpmn:incoming>SequenceFlow_11c35oq</bpmn:incoming>
|
||||
|
@ -130,7 +130,7 @@ Autoconverted link https://github.com/nodeca/pica (enable linkify to see)
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>SequenceFlow_0641sh6</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_0t29gjo</bpmn:outgoing>
|
||||
<bpmn:script>scripts.FactService</bpmn:script>
|
||||
<bpmn:script>FactService</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:endEvent id="EndEvent_0u1cgrf">
|
||||
<bpmn:documentation># Great Job!
|
39
tests/data/study_details/study_details.bpmn
Normal file
39
tests/data/study_details/study_details.bpmn
Normal file
@ -0,0 +1,39 @@
|
||||
<?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:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_0kmksnn" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.4.1">
|
||||
<bpmn:process id="Process_0exnnpv" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>SequenceFlow_1nfe5m9</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1nfe5m9" sourceRef="StartEvent_1" targetRef="Task_Script_Load_Study_Details" />
|
||||
<bpmn:scriptTask id="Task_Script_Load_Study_Details" name="Load Study Info">
|
||||
<bpmn:incoming>SequenceFlow_1nfe5m9</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_1bqiin0</bpmn:outgoing>
|
||||
<bpmn:script>StudyInfo info</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1bqiin0" sourceRef="Task_Script_Load_Study_Details" targetRef="EndEvent_171dj09" />
|
||||
<bpmn:endEvent id="EndEvent_171dj09">
|
||||
<bpmn:incoming>SequenceFlow_1bqiin0</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0exnnpv">
|
||||
<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_1nfe5m9_di" bpmnElement="SequenceFlow_1nfe5m9">
|
||||
<di:waypoint x="215" y="117" />
|
||||
<di:waypoint x="270" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="ScriptTask_1mp6xid_di" bpmnElement="Task_Script_Load_Study_Details">
|
||||
<dc:Bounds x="270" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1bqiin0_di" bpmnElement="SequenceFlow_1bqiin0">
|
||||
<di:waypoint x="370" y="117" />
|
||||
<di:waypoint x="402" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="EndEvent_171dj09_di" bpmnElement="EndEvent_171dj09">
|
||||
<dc:Bounds x="402" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
@ -7,7 +7,6 @@ from crc.models.study import StudyModel, StudyModelSchema
|
||||
from crc.models.protocol_builder import ProtocolBuilderStatus
|
||||
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel, WorkflowStatus, \
|
||||
WorkflowApiSchema
|
||||
from services.protocol_builder import ProtocolBuilderService
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
from crc import session
|
||||
from crc.models.file import FileModelSchema
|
||||
from crc.models.study import StudyModel
|
||||
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel, \
|
||||
WorkflowApiSchema, WorkflowStatus, Task
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
|
||||
@ -12,7 +14,8 @@ class TestTasksApi(BaseTest):
|
||||
|
||||
def create_workflow(self, workflow_name):
|
||||
study = session.query(StudyModel).first()
|
||||
spec = session.query(WorkflowSpecModel).filter_by(id=workflow_name).first()
|
||||
spec = self.load_test_spec(workflow_name)
|
||||
processor = WorkflowProcessor.create(study.id, spec.id)
|
||||
rv = self.app.post(
|
||||
'/v1.0/study/%i/workflows' % study.id,
|
||||
headers=self.logged_in_headers(),
|
||||
@ -186,8 +189,6 @@ class TestTasksApi(BaseTest):
|
||||
task.process_documentation(docs)
|
||||
self.assertEqual("This test works", task.documentation)
|
||||
|
||||
|
||||
|
||||
def test_get_documentation_populated_in_end(self):
|
||||
self.load_example_data()
|
||||
workflow = self.create_workflow('random_fact')
|
||||
@ -201,3 +202,7 @@ class TestTasksApi(BaseTest):
|
||||
self.assertEqual("EndEvent_0u1cgrf", workflow_api.next_task['name'])
|
||||
self.assertIsNotNone(workflow_api.next_task['documentation'])
|
||||
self.assertTrue("norris" in workflow_api.next_task['documentation'])
|
||||
|
||||
|
||||
|
||||
# response = ProtocolBuilderService.get_study_details(self.test_study_id)
|
||||
|
@ -22,7 +22,8 @@ class TestStudyApi(BaseTest):
|
||||
|
||||
def test_render_docx(self):
|
||||
filepath = os.path.join(app.root_path, '..', 'tests', 'data', 'table.docx')
|
||||
template_data = {"hippa": [{"option": "Name", "selected": True, "stored": ["Record at UVA", "Stored Long Term"]},
|
||||
template_data = {"hippa": [
|
||||
{"option": "Name", "selected": True, "stored": ["Record at UVA", "Stored Long Term"]},
|
||||
{"option": "Address", "selected": False},
|
||||
{"option": "Phone", "selected": True, "stored": ["Send or Transmit outside of UVA"]}]}
|
||||
with open(filepath, 'rb') as f:
|
||||
|
@ -1,5 +1,6 @@
|
||||
import string
|
||||
import random
|
||||
from unittest.mock import patch
|
||||
|
||||
from SpiffWorkflow.bpmn.specs.EndEvent import EndEvent
|
||||
|
||||
@ -29,7 +30,7 @@ class TestWorkflowProcessor(BaseTest):
|
||||
|
||||
def test_create_and_complete_workflow(self):
|
||||
self.load_example_data()
|
||||
workflow_spec_model = session.query(WorkflowSpecModel).filter_by(id="random_fact").first()
|
||||
workflow_spec_model = self.load_test_spec("random_fact")
|
||||
study = session.query(StudyModel).first()
|
||||
processor = WorkflowProcessor.create(study.id, workflow_spec_model.id)
|
||||
self.assertEqual(study.id, processor.bpmn_workflow.data[WorkflowProcessor.STUDY_ID_KEY])
|
||||
@ -54,9 +55,9 @@ class TestWorkflowProcessor(BaseTest):
|
||||
def test_workflow_with_dmn(self):
|
||||
self.load_example_data()
|
||||
study = session.query(StudyModel).first()
|
||||
workflow_spec_model = self.load_test_spec("decision_table")
|
||||
files = session.query(FileModel).filter_by(workflow_spec_id='decision_table').all()
|
||||
self.assertEqual(2, len(files))
|
||||
workflow_spec_model = session.query(WorkflowSpecModel).filter_by(id="decision_table").first()
|
||||
processor = WorkflowProcessor.create(study.id, workflow_spec_model.id)
|
||||
self.assertEqual(WorkflowStatus.user_input_required, processor.get_status())
|
||||
next_user_tasks = processor.next_user_tasks()
|
||||
@ -79,7 +80,7 @@ class TestWorkflowProcessor(BaseTest):
|
||||
|
||||
def test_workflow_with_parallel_forms(self):
|
||||
self.load_example_data()
|
||||
workflow_spec_model = session.query(WorkflowSpecModel).filter_by(id="parallel_tasks").first()
|
||||
workflow_spec_model = self.load_test_spec("parallel_tasks")
|
||||
study = session.query(StudyModel).first()
|
||||
processor = WorkflowProcessor.create(study.id, workflow_spec_model.id)
|
||||
self.assertEqual(WorkflowStatus.user_input_required, processor.get_status())
|
||||
@ -121,7 +122,7 @@ class TestWorkflowProcessor(BaseTest):
|
||||
def test_workflow_processor_knows_the_text_task_even_when_parallel(self):
|
||||
self.load_example_data()
|
||||
study = session.query(StudyModel).first()
|
||||
workflow_spec_model = session.query(WorkflowSpecModel).filter_by(id="parallel_tasks").first()
|
||||
workflow_spec_model = self.load_test_spec("parallel_tasks")
|
||||
processor = WorkflowProcessor.create(study.id, workflow_spec_model.id)
|
||||
self.assertEqual(WorkflowStatus.user_input_required, processor.get_status())
|
||||
next_user_tasks = processor.next_user_tasks()
|
||||
@ -141,7 +142,7 @@ class TestWorkflowProcessor(BaseTest):
|
||||
|
||||
def test_workflow_processor_returns_next_task_as_end_task_if_complete(self):
|
||||
self.load_example_data()
|
||||
workflow_spec_model = session.query(WorkflowSpecModel).filter_by(id="random_fact").first()
|
||||
workflow_spec_model = self.load_test_spec("random_fact")
|
||||
study = session.query(StudyModel).first()
|
||||
processor = WorkflowProcessor.create(study.id, workflow_spec_model.id)
|
||||
processor.do_engine_steps()
|
||||
@ -196,3 +197,20 @@ class TestWorkflowProcessor(BaseTest):
|
||||
self.assertIsNotNone(file_data.data)
|
||||
self.assertTrue(len(file_data.data) > 0)
|
||||
# Not going any farther here, assuming this is tested in libraries correctly.
|
||||
|
||||
def test_load_study_information(self):
|
||||
""" Test a workflow that includes requests to pull in Study Details."""
|
||||
|
||||
self.load_example_data()
|
||||
study = session.query(StudyModel).first()
|
||||
workflow_spec_model = self.load_test_spec("study_details")
|
||||
processor = WorkflowProcessor.create(study.id, workflow_spec_model.id)
|
||||
processor.do_engine_steps()
|
||||
task = processor.bpmn_workflow.last_task
|
||||
self.assertIsNotNone(task.data)
|
||||
self.assertIn("study", task.data)
|
||||
self.assertIn("info", task.data["study"])
|
||||
self.assertIn("title", task.data["study"]["info"])
|
||||
self.assertIn("last_updated", task.data["study"]["info"])
|
||||
self.assertIn("sponsor", task.data["study"]["info"])
|
||||
|
||||
|
@ -47,7 +47,7 @@ class TestWorkflowSpec(BaseTest):
|
||||
def test_delete_workflow_specification(self):
|
||||
self.load_example_data()
|
||||
spec_id = 'random_fact'
|
||||
|
||||
self.load_test_spec(spec_id)
|
||||
num_specs_before = session.query(WorkflowSpecModel).filter_by(id=spec_id).count()
|
||||
self.assertEqual(num_specs_before, 1)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user