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:
Dan Funk 2020-03-03 13:50:22 -05:00
parent 305118e90e
commit 7194d7d374
25 changed files with 211 additions and 159 deletions

View File

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

View File

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

View File

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

View File

@ -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",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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>

View File

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

View File

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

View File

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

View File

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

View File

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