Merge pull request #264 from sartography/bug/243_complete_all
Bug/243 complete all
This commit is contained in:
commit
14386b8ba9
|
@ -904,6 +904,12 @@ paths:
|
|||
description: Terminate the loop on a looping task
|
||||
schema:
|
||||
type: boolean
|
||||
- name: update_all
|
||||
in: query
|
||||
required: false
|
||||
description: In the case of a multi-instance user task, update all tasks with the submitted values.
|
||||
schema:
|
||||
type: boolean
|
||||
put:
|
||||
operationId: crc.api.workflow.update_task
|
||||
summary: Exclusively for User Tasks, submits form data as a flat set of key/values.
|
||||
|
|
|
@ -179,7 +179,7 @@ def set_current_task(workflow_id, task_id):
|
|||
return WorkflowApiSchema().dump(workflow_api_model)
|
||||
|
||||
|
||||
def update_task(workflow_id, task_id, body, terminate_loop=None):
|
||||
def update_task(workflow_id, task_id, body, terminate_loop=None, update_all=False):
|
||||
workflow_model = session.query(WorkflowModel).filter_by(id=workflow_id).first()
|
||||
if workflow_model is None:
|
||||
raise ApiError("invalid_workflow_id", "The given workflow id is not valid.", status_code=404)
|
||||
|
@ -191,6 +191,8 @@ def update_task(workflow_id, task_id, body, terminate_loop=None):
|
|||
task_id = uuid.UUID(task_id)
|
||||
spiff_task = processor.bpmn_workflow.get_task(task_id)
|
||||
_verify_user_and_role(processor, spiff_task)
|
||||
user = UserService.current_user(allow_admin_impersonate=False) # Always log as the real user.
|
||||
|
||||
if not spiff_task:
|
||||
raise ApiError("empty_task", "Processor failed to obtain task.", status_code=404)
|
||||
if spiff_task.state != spiff_task.READY:
|
||||
|
@ -199,20 +201,39 @@ def update_task(workflow_id, task_id, body, terminate_loop=None):
|
|||
|
||||
if terminate_loop:
|
||||
spiff_task.terminate_loop()
|
||||
spiff_task.update_data(body)
|
||||
processor.complete_task(spiff_task)
|
||||
processor.do_engine_steps()
|
||||
processor.save()
|
||||
|
||||
# Log the action, and any pending task assignments in the event of lanes in the workflow.
|
||||
user = UserService.current_user(allow_admin_impersonate=False) # Always log as the real user.
|
||||
WorkflowService.log_task_action(user.uid, processor, spiff_task, WorkflowService.TASK_ACTION_COMPLETE)
|
||||
# Extract the details specific to the form submitted
|
||||
form_data = WorkflowService().extract_form_data(body, spiff_task)
|
||||
|
||||
# Update the task
|
||||
__update_task(processor, spiff_task, form_data, user)
|
||||
|
||||
# If we need to update all tasks, then get the next ready task and if it a multi-instance with the same
|
||||
# task spec, complete that form as well.
|
||||
if update_all:
|
||||
last_index = spiff_task.task_info()["mi_index"]
|
||||
next_task = processor.next_task()
|
||||
while next_task and next_task.task_info()["mi_index"] > last_index:
|
||||
__update_task(processor, next_task, form_data, user)
|
||||
last_index = next_task.task_info()["mi_index"]
|
||||
next_task = processor.next_task()
|
||||
|
||||
WorkflowService.update_task_assignments(processor)
|
||||
|
||||
workflow_api_model = WorkflowService.processor_to_workflow_api(processor)
|
||||
return WorkflowApiSchema().dump(workflow_api_model)
|
||||
|
||||
|
||||
def __update_task(processor, task, data, user):
|
||||
"""All the things that need to happen when we complete a form, abstracted
|
||||
here because we need to do it multiple times when completing all tasks in
|
||||
a multi-instance task"""
|
||||
task.update_data(data)
|
||||
processor.complete_task(task)
|
||||
processor.do_engine_steps()
|
||||
processor.save()
|
||||
WorkflowService.log_task_action(user.uid, processor, task, WorkflowService.TASK_ACTION_COMPLETE)
|
||||
|
||||
|
||||
def list_workflow_spec_categories():
|
||||
schema = WorkflowSpecCategoryModelSchema(many=True)
|
||||
return schema.dump(session.query(WorkflowSpecCategoryModel).all())
|
||||
|
|
|
@ -16,6 +16,7 @@ from SpiffWorkflow.bpmn.specs.UserTask import UserTask
|
|||
from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask
|
||||
from SpiffWorkflow.specs import CancelTask, StartTask, MultiChoice
|
||||
from SpiffWorkflow.util.deep_merge import DeepMerge
|
||||
from box import Box
|
||||
from jinja2 import Template
|
||||
|
||||
from crc import db, app
|
||||
|
@ -709,7 +710,8 @@ class WorkflowService(object):
|
|||
|
||||
@staticmethod
|
||||
def extract_form_data(latest_data, task):
|
||||
"""Removes data from latest_data that would be added by the child task or any of its children."""
|
||||
"""Extracts data from the latest_data that is directly related to the form that is being
|
||||
submitted."""
|
||||
data = {}
|
||||
|
||||
if hasattr(task.task_spec, 'form'):
|
||||
|
@ -721,16 +723,49 @@ class WorkflowService(object):
|
|||
group = field.get_property(Task.FIELD_PROP_REPEAT)
|
||||
if group in latest_data:
|
||||
data[group] = latest_data[group]
|
||||
elif isinstance(task.task_spec, MultiInstanceTask):
|
||||
group = task.task_spec.elementVar
|
||||
if group in latest_data:
|
||||
data[group] = latest_data[group]
|
||||
else:
|
||||
if field.id in latest_data:
|
||||
data[field.id] = latest_data[field.id]
|
||||
|
||||
value = WorkflowService.get_dot_value(field.id, latest_data)
|
||||
if value is not None:
|
||||
WorkflowService.set_dot_value(field.id, value, data)
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def get_dot_value(path, source):
|
||||
### Given a path in dot notation, uas as 'fruit.type' tries to find that value in
|
||||
### the source, but looking deep in the dictionary.
|
||||
paths = path.split(".") # [a,b,c]
|
||||
s = source
|
||||
index = 0
|
||||
for p in paths:
|
||||
index += 1
|
||||
if isinstance(s, dict) and p in s:
|
||||
if index == len(paths):
|
||||
return s[p]
|
||||
else:
|
||||
s = s[p]
|
||||
if path in source:
|
||||
return source[path]
|
||||
return None
|
||||
|
||||
|
||||
@staticmethod
|
||||
def set_dot_value(path, value, target):
|
||||
### Given a path in dot notation, such as "fruit.type", and a value "apple", will
|
||||
### set the value in the target dictionary, as target["fruit"]["type"]="apple"
|
||||
destination = target
|
||||
paths = path.split(".") # [a,b,c]
|
||||
index = 0
|
||||
for p in paths:
|
||||
index += 1
|
||||
if p not in destination:
|
||||
if index == len(paths):
|
||||
destination[p] = value
|
||||
else:
|
||||
destination[p] = {}
|
||||
destination = destination[p]
|
||||
return target
|
||||
|
||||
|
||||
@staticmethod
|
||||
def process_workflows_for_cancels(study_id):
|
||||
workflows = db.session.query(WorkflowModel).filter_by(study_id=study_id).all()
|
||||
|
|
|
@ -5,7 +5,7 @@ services:
|
|||
volumes:
|
||||
- $HOME/docker/volumes/postgres:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 5003:5432
|
||||
- 5432:5432
|
||||
environment:
|
||||
- POSTGRES_USER=${DB_USER}
|
||||
- POSTGRES_PASSWORD=${DB_PASS}
|
||||
|
|
|
@ -68,24 +68,24 @@ class BaseTest(unittest.TestCase):
|
|||
|
||||
studies = [
|
||||
{
|
||||
'id':0,
|
||||
'title':'The impact of fried pickles on beer consumption in bipedal software developers.',
|
||||
'last_updated':datetime.datetime.now(),
|
||||
'status':StudyStatus.in_progress,
|
||||
'primary_investigator_id':'dhf8r',
|
||||
'sponsor':'Sartography Pharmaceuticals',
|
||||
'ind_number':'1234',
|
||||
'user_uid':'dhf8r'
|
||||
'id': 0,
|
||||
'title': 'The impact of fried pickles on beer consumption in bipedal software developers.',
|
||||
'last_updated': datetime.datetime.now(),
|
||||
'status': StudyStatus.in_progress,
|
||||
'primary_investigator_id': 'dhf8r',
|
||||
'sponsor': 'Sartography Pharmaceuticals',
|
||||
'ind_number': '1234',
|
||||
'user_uid': 'dhf8r'
|
||||
},
|
||||
{
|
||||
'id':1,
|
||||
'title':'Requirement of hippocampal neurogenesis for the behavioral effects of soft pretzels',
|
||||
'last_updated':datetime.datetime.now(),
|
||||
'status':StudyStatus.in_progress,
|
||||
'primary_investigator_id':'dhf8r',
|
||||
'sponsor':'Makerspace & Co.',
|
||||
'ind_number':'5678',
|
||||
'user_uid':'dhf8r'
|
||||
'id': 1,
|
||||
'title': 'Requirement of hippocampal neurogenesis for the behavioral effects of soft pretzels',
|
||||
'last_updated': datetime.datetime.now(),
|
||||
'status': StudyStatus.in_progress,
|
||||
'primary_investigator_id': 'dhf8r',
|
||||
'sponsor': 'Makerspace & Co.',
|
||||
'ind_number': '5678',
|
||||
'user_uid': 'dhf8r'
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -141,7 +141,6 @@ class BaseTest(unittest.TestCase):
|
|||
session.execute("delete from workflow; delete from file_data; delete from file; delete from workflow_spec;")
|
||||
session.commit()
|
||||
|
||||
|
||||
def load_example_data(self, use_crc_data=False, use_rrt_data=False):
|
||||
"""use_crc_data will cause this to load the mammoth collection of documents
|
||||
we built up developing crc, use_rrt_data will do the same for hte rrt project,
|
||||
|
@ -219,7 +218,6 @@ class BaseTest(unittest.TestCase):
|
|||
data = myfile.read()
|
||||
return data
|
||||
|
||||
|
||||
def assert_success(self, rv, msg=""):
|
||||
try:
|
||||
data = json.loads(rv.get_data(as_text=True))
|
||||
|
@ -361,7 +359,7 @@ class BaseTest(unittest.TestCase):
|
|||
def get_workflow_api(self, workflow, do_engine_steps=True, user_uid="dhf8r"):
|
||||
user = session.query(UserModel).filter_by(uid=user_uid).first()
|
||||
self.assertIsNotNone(user)
|
||||
url = (f'/v1.0/workflow/{workflow.id}'
|
||||
url = (f'/v1.0/workflow/{workflow.id}'
|
||||
f'?do_engine_steps={str(do_engine_steps)}')
|
||||
workflow_api = self.get_workflow_common(url, user)
|
||||
self.assertEqual(workflow.workflow_spec_id, workflow_api.workflow_spec_id)
|
||||
|
@ -370,13 +368,14 @@ class BaseTest(unittest.TestCase):
|
|||
def restart_workflow_api(self, workflow, clear_data=False, user_uid="dhf8r"):
|
||||
user = session.query(UserModel).filter_by(uid=user_uid).first()
|
||||
self.assertIsNotNone(user)
|
||||
url = (f'/v1.0/workflow/{workflow.id}/restart'
|
||||
url = (f'/v1.0/workflow/{workflow.id}/restart'
|
||||
f'?clear_data={str(clear_data)}')
|
||||
workflow_api = self.get_workflow_common(url, user)
|
||||
self.assertEqual(workflow.workflow_spec_id, workflow_api.workflow_spec_id)
|
||||
return workflow_api
|
||||
|
||||
def complete_form(self, workflow_in, task_in, dict_data, error_code=None, terminate_loop=None, user_uid="dhf8r"):
|
||||
def complete_form(self, workflow_in, task_in, dict_data, update_all=False, error_code=None, terminate_loop=None,
|
||||
user_uid="dhf8r"):
|
||||
prev_completed_task_count = workflow_in.completed_tasks
|
||||
if isinstance(task_in, dict):
|
||||
task_id = task_in["id"]
|
||||
|
@ -385,16 +384,16 @@ class BaseTest(unittest.TestCase):
|
|||
|
||||
user = session.query(UserModel).filter_by(uid=user_uid).first()
|
||||
self.assertIsNotNone(user)
|
||||
args = ""
|
||||
if terminate_loop:
|
||||
rv = self.app.put('/v1.0/workflow/%i/task/%s/data?terminate_loop=true' % (workflow_in.id, task_id),
|
||||
headers=self.logged_in_headers(user=user),
|
||||
content_type="application/json",
|
||||
data=json.dumps(dict_data))
|
||||
else:
|
||||
rv = self.app.put('/v1.0/workflow/%i/task/%s/data' % (workflow_in.id, task_id),
|
||||
headers=self.logged_in_headers(user=user),
|
||||
content_type="application/json",
|
||||
data=json.dumps(dict_data))
|
||||
args += "?terminate_loop=true"
|
||||
if update_all:
|
||||
args += "?update_all=true"
|
||||
|
||||
rv = self.app.put('/v1.0/workflow/%i/task/%s/data%s' % (workflow_in.id, task_id, args),
|
||||
headers=self.logged_in_headers(user=user),
|
||||
content_type="application/json",
|
||||
data=json.dumps(dict_data))
|
||||
if error_code:
|
||||
self.assert_failure(rv, error_code=error_code)
|
||||
return
|
||||
|
@ -408,7 +407,7 @@ class BaseTest(unittest.TestCase):
|
|||
# branches may be pruned. As we hit parallel Multi-Instance new tasks may be created...
|
||||
self.assertIsNotNone(workflow.total_tasks)
|
||||
# presumably, we also need to deal with sequential items here too . .
|
||||
if not task_in.multi_instance_type == 'looping':
|
||||
if not task_in.multi_instance_type == 'looping' and not update_all:
|
||||
self.assertEqual(prev_completed_task_count + 1, workflow.completed_tasks)
|
||||
|
||||
# Assure a record exists in the Task Events
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?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_1xiske1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.2.0">
|
||||
<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_1xiske1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.3">
|
||||
<bpmn:process id="Process_TestBooleanDefault" name="Test Boolean Default" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>Flow_1x41riu</bpmn:outgoing>
|
||||
|
@ -20,8 +20,7 @@
|
|||
<bpmn:sequenceFlow id="Flow_0m31ypa" sourceRef="Activity_SelectBoolean" targetRef="Activity_GoodBye" />
|
||||
<bpmn:manualTask id="Activity_GoodBye" name="Good Bye">
|
||||
<bpmn:documentation><H1>Good Bye</H1>
|
||||
<div><span>Pick One: {% if pick_one %}{{ pick_one}}{% endif %} </span></div>
|
||||
</bpmn:documentation>
|
||||
<div><span>Pick One: {% if pick_one %}{{ pick_one}}{% endif %} </span></div></bpmn:documentation>
|
||||
<bpmn:incoming>Flow_0m31ypa</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0f3gndz</bpmn:outgoing>
|
||||
</bpmn:manualTask>
|
||||
|
@ -29,13 +28,7 @@
|
|||
<bpmn:incoming>Flow_0f3gndz</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="Flow_0f3gndz" sourceRef="Activity_GoodBye" targetRef="Event_0rgpb6o" />
|
||||
<bpmn:sequenceFlow id="Flow_1x41riu" sourceRef="StartEvent_1" targetRef="Activity_Hello" />
|
||||
<bpmn:manualTask id="Activity_Hello" name="Hello">
|
||||
<bpmn:documentation><H1>Hello</H1></bpmn:documentation>
|
||||
<bpmn:incoming>Flow_1x41riu</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1i32jb7</bpmn:outgoing>
|
||||
</bpmn:manualTask>
|
||||
<bpmn:sequenceFlow id="Flow_1i32jb7" sourceRef="Activity_Hello" targetRef="Activity_PreData" />
|
||||
<bpmn:sequenceFlow id="Flow_1x41riu" sourceRef="StartEvent_1" targetRef="Set_Default" />
|
||||
<bpmn:sequenceFlow id="Flow_0zp5mss" sourceRef="Activity_PreData" targetRef="Activity_SelectBoolean" />
|
||||
<bpmn:scriptTask id="Activity_PreData" name="Pre Data">
|
||||
<bpmn:incoming>Flow_1i32jb7</bpmn:incoming>
|
||||
|
@ -43,17 +36,24 @@
|
|||
<bpmn:script>if not 'yes_no' in globals():
|
||||
yes_no = False</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_1i32jb7" sourceRef="Set_Default" targetRef="Activity_PreData" />
|
||||
<bpmn:userTask id="Set_Default" name="Hello" camunda:formKey="set_default">
|
||||
<bpmn:documentation><H1>Hello</H1></bpmn:documentation>
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="yes_no" label="Set A default" type="boolean">
|
||||
<camunda:validation>
|
||||
<camunda:constraint name="required" config="true" />
|
||||
</camunda:validation>
|
||||
</camunda:formField>
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_1x41riu</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1i32jb7</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_TestBooleanDefault">
|
||||
<bpmndi:BPMNEdge id="Flow_0f3gndz_di" bpmnElement="Flow_0f3gndz">
|
||||
<di:waypoint x="820" y="117" />
|
||||
<di:waypoint x="882" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0m31ypa_di" bpmnElement="Flow_0m31ypa">
|
||||
<di:waypoint x="662" y="117" />
|
||||
<di:waypoint x="720" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0zp5mss_di" bpmnElement="Flow_0zp5mss">
|
||||
<di:waypoint x="500" y="117" />
|
||||
<di:waypoint x="562" y="117" />
|
||||
|
@ -66,6 +66,17 @@
|
|||
<di:waypoint x="188" y="117" />
|
||||
<di:waypoint x="240" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0f3gndz_di" bpmnElement="Flow_0f3gndz">
|
||||
<di:waypoint x="820" y="117" />
|
||||
<di:waypoint x="882" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0m31ypa_di" bpmnElement="Flow_0m31ypa">
|
||||
<di:waypoint x="662" y="117" />
|
||||
<di:waypoint x="720" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="152" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0vujv1w_di" bpmnElement="Activity_SelectBoolean">
|
||||
<dc:Bounds x="562" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
|
@ -75,15 +86,12 @@
|
|||
<bpmndi:BPMNShape id="Event_0rgpb6o_di" bpmnElement="Event_0rgpb6o">
|
||||
<dc:Bounds x="882" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0vu3ozg_di" bpmnElement="Activity_Hello">
|
||||
<dc:Bounds x="240" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="152" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0ug5gxt_di" bpmnElement="Activity_PreData">
|
||||
<dc:Bounds x="400" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0hks5xj_di" bpmnElement="Set_Default">
|
||||
<dc:Bounds x="240" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
||||
|
|
|
@ -27,7 +27,7 @@ email(subject=subject,recipients=recipients)</bpmn:script>
|
|||
<bpmn:sequenceFlow id="Flow_1synsig" sourceRef="StartEvent_1" targetRef="Activity_1l9vih3" />
|
||||
<bpmn:sequenceFlow id="Flow_1xlrgne" sourceRef="Activity_0s5v97n" targetRef="Event_0izrcj4" />
|
||||
<bpmn:sequenceFlow id="Flow_08n2npe" sourceRef="Activity_1l9vih3" targetRef="Activity_0s5v97n" />
|
||||
<bpmn:userTask id="Activity_1l9vih3" name="Set Recipients">
|
||||
<bpmn:userTask id="Activity_1l9vih3" name="Set Recipients" camunda:formKey="MyFormKey">
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="ApprvlApprvr1" label="Approver" type="string" />
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?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:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" id="Definitions_17fwemw" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.0">
|
||||
<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:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" id="Definitions_17fwemw" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.3">
|
||||
<bpmn:process id="MultiInstance" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1" name="StartEvent_1">
|
||||
<bpmn:outgoing>Flow_0t6p1sb</bpmn:outgoing>
|
||||
|
@ -15,7 +15,7 @@
|
|||
## Role: {{investigator.type_full}}</bpmn:documentation>
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="email" label="Email Address:" type="string" />
|
||||
<camunda:formField id="investigator.email" label="Email Address:" type="string" />
|
||||
</camunda:formData>
|
||||
<camunda:properties>
|
||||
<camunda:property name="display_name" value="investigator.label" />
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
<?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_0l37fag" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.2.0">
|
||||
<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_0l37fag" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.3">
|
||||
<bpmn:process id="Process_TestValueExpression" name="Test Value Expression" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>Flow_1nc3qi5</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="Flow_1nc3qi5" sourceRef="StartEvent_1" targetRef="Activity_Hello" />
|
||||
<bpmn:sequenceFlow id="Flow_1t2lo17" sourceRef="Activity_Hello" targetRef="Activity_PreData" />
|
||||
<bpmn:sequenceFlow id="Flow_1nc3qi5" sourceRef="StartEvent_1" targetRef="Activity_Set_Expression" />
|
||||
<bpmn:sequenceFlow id="Flow_1t2lo17" sourceRef="Activity_Set_Expression" targetRef="Activity_PreData" />
|
||||
<bpmn:sequenceFlow id="Flow_1hhfj67" sourceRef="Activity_PreData" targetRef="Activity_Data" />
|
||||
<bpmn:manualTask id="Activity_Hello" name="Hello">
|
||||
<bpmn:documentation><H1>Hello</H1></bpmn:documentation>
|
||||
<bpmn:incoming>Flow_1nc3qi5</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1t2lo17</bpmn:outgoing>
|
||||
</bpmn:manualTask>
|
||||
<bpmn:scriptTask id="Activity_PreData" name="Pre Data">
|
||||
<bpmn:incoming>Flow_1t2lo17</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1hhfj67</bpmn:outgoing>
|
||||
|
@ -43,34 +38,48 @@
|
|||
<bpmn:incoming>Flow_057as2q</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="Flow_057as2q" sourceRef="Activity_GoodBye" targetRef="Event_06wbkzi" />
|
||||
<bpmn:userTask id="Activity_Set_Expression" name="Hello" camunda:formKey="value_expression_form">
|
||||
<bpmn:documentation><H1>Hello</H1></bpmn:documentation>
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="value_expression_value" type="string">
|
||||
<camunda:validation>
|
||||
<camunda:constraint name="required" config="true" />
|
||||
</camunda:validation>
|
||||
</camunda:formField>
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_1nc3qi5</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1t2lo17</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_TestValueExpression">
|
||||
<bpmndi:BPMNEdge id="Flow_1nc3qi5_di" bpmnElement="Flow_1nc3qi5">
|
||||
<di:waypoint x="215" y="117" />
|
||||
<di:waypoint x="270" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1t2lo17_di" bpmnElement="Flow_1t2lo17">
|
||||
<di:waypoint x="370" y="117" />
|
||||
<di:waypoint x="431" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1hhfj67_di" bpmnElement="Flow_1hhfj67">
|
||||
<di:waypoint x="531" y="117" />
|
||||
<di:waypoint x="590" y="117" />
|
||||
<bpmndi:BPMNEdge id="Flow_057as2q_di" bpmnElement="Flow_057as2q">
|
||||
<di:waypoint x="850" y="117" />
|
||||
<di:waypoint x="912" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1skkg5a_di" bpmnElement="Flow_1skkg5a">
|
||||
<di:waypoint x="690" y="117" />
|
||||
<di:waypoint x="750" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_057as2q_di" bpmnElement="Flow_057as2q">
|
||||
<di:waypoint x="850" y="117" />
|
||||
<di:waypoint x="912" y="117" />
|
||||
<bpmndi:BPMNEdge id="Flow_1hhfj67_di" bpmnElement="Flow_1hhfj67">
|
||||
<di:waypoint x="531" y="117" />
|
||||
<di:waypoint x="590" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1t2lo17_di" bpmnElement="Flow_1t2lo17">
|
||||
<di:waypoint x="370" y="117" />
|
||||
<di:waypoint x="431" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1nc3qi5_di" bpmnElement="Flow_1nc3qi5">
|
||||
<di:waypoint x="215" y="117" />
|
||||
<di:waypoint x="270" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="179" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0hi68vh_di" bpmnElement="Activity_Hello">
|
||||
<dc:Bounds x="270" y="77" width="100" height="80" />
|
||||
<bpmndi:BPMNShape id="Activity_115dslj_di" bpmnElement="Activity_PreData">
|
||||
<dc:Bounds x="431" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1i60o9l_di" bpmnElement="Activity_Data">
|
||||
<dc:Bounds x="590" y="77" width="100" height="80" />
|
||||
|
@ -81,8 +90,8 @@
|
|||
<bpmndi:BPMNShape id="Event_06wbkzi_di" bpmnElement="Event_06wbkzi">
|
||||
<dc:Bounds x="912" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_115dslj_di" bpmnElement="Activity_PreData">
|
||||
<dc:Bounds x="431" y="77" width="100" height="80" />
|
||||
<bpmndi:BPMNShape id="Activity_1wwwyog_di" bpmnElement="Activity_Set_Expression">
|
||||
<dc:Bounds x="270" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
|
|
|
@ -1,13 +1,7 @@
|
|||
from unittest.mock import patch
|
||||
|
||||
from crc import session
|
||||
from crc.models.api_models import MultiInstanceType
|
||||
from crc.models.study import StudyModel
|
||||
from crc.models.workflow import WorkflowStatus
|
||||
from tests.base_test import BaseTest
|
||||
from crc.services.study_service import StudyService
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
from crc.services.workflow_service import WorkflowService
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
|
||||
class TestWorkflowProcessorLoopingTask(BaseTest):
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
import json
|
||||
import random
|
||||
from unittest.mock import patch
|
||||
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
from crc import session, app
|
||||
from crc.models.api_models import WorkflowApiSchema, MultiInstanceType
|
||||
from crc.models.workflow import WorkflowStatus
|
||||
from example_data import ExampleDataLoader
|
||||
|
||||
|
||||
class TestMultiinstanceTasksApi(BaseTest):
|
||||
|
||||
|
||||
@patch('crc.services.protocol_builder.requests.get')
|
||||
def test_multi_instance_task(self, mock_get):
|
||||
|
||||
ExampleDataLoader().load_reference_documents()
|
||||
|
||||
# Enable the protocol builder.
|
||||
app.config['PB_ENABLED'] = True
|
||||
|
||||
# This depends on getting a list of investigators back from the protocol builder.
|
||||
mock_get.return_value.ok = True
|
||||
mock_get.return_value.text = self.protocol_builder_response('investigators.json')
|
||||
|
||||
workflow = self.create_workflow('multi_instance')
|
||||
|
||||
# get the first form in the two form workflow.
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
navigation = self.get_workflow_api(workflow).navigation
|
||||
self.assertEqual(5, len(navigation)) # Start task, form_task, multi_task, end task
|
||||
self.assertEqual("UserTask", workflow.next_task.type)
|
||||
self.assertEqual(MultiInstanceType.sequential.value, workflow.next_task.multi_instance_type)
|
||||
self.assertEqual(5, workflow.next_task.multi_instance_count)
|
||||
|
||||
# Assure that the names for each task are properly updated, so they aren't all the same.
|
||||
self.assertEqual("Primary Investigator", workflow.next_task.title)
|
||||
|
||||
|
||||
@patch('crc.services.protocol_builder.requests.get')
|
||||
def test_parallel_multi_instance(self, mock_get):
|
||||
|
||||
# Assure we get nine investigators back from the API Call, as set in the investigators.json file.
|
||||
app.config['PB_ENABLED'] = True
|
||||
mock_get.return_value.ok = True
|
||||
mock_get.return_value.text = self.protocol_builder_response('investigators.json')
|
||||
|
||||
ExampleDataLoader().load_reference_documents()
|
||||
|
||||
workflow = self.create_workflow('multi_instance_parallel')
|
||||
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
self.assertEqual(9, len(workflow_api.navigation))
|
||||
ready_items = [nav for nav in workflow_api.navigation if nav.state == "READY"]
|
||||
self.assertEqual(5, len(ready_items))
|
||||
|
||||
self.assertEqual("UserTask", workflow_api.next_task.type)
|
||||
self.assertEqual("MultiInstanceTask",workflow_api.next_task.name)
|
||||
self.assertEqual("Primary Investigator", workflow_api.next_task.title)
|
||||
|
||||
for i in random.sample(range(5), 5):
|
||||
task_id = ready_items[i].task_id
|
||||
rv = self.app.put('/v1.0/workflow/%i/task/%s/set_token' % (workflow.id, task_id),
|
||||
headers=self.logged_in_headers(),
|
||||
content_type="application/json")
|
||||
self.assert_success(rv)
|
||||
json_data = json.loads(rv.get_data(as_text=True))
|
||||
workflow = WorkflowApiSchema().load(json_data)
|
||||
data = workflow.next_task.data
|
||||
data['investigator']['email'] = "dhf8r@virginia.edu"
|
||||
self.complete_form(workflow, workflow.next_task, data)
|
||||
#tasks = self.get_workflow_api(workflow).user_tasks
|
||||
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
self.assertEqual(WorkflowStatus.complete, workflow.status)
|
||||
|
||||
|
||||
@patch('crc.services.protocol_builder.requests.get')
|
||||
def test_parallel_multi_instance_update_all(self, mock_get):
|
||||
|
||||
# Assure we get nine investigators back from the API Call, as set in the investigators.json file.
|
||||
app.config['PB_ENABLED'] = True
|
||||
mock_get.return_value.ok = True
|
||||
mock_get.return_value.text = self.protocol_builder_response('investigators.json')
|
||||
|
||||
ExampleDataLoader().load_reference_documents()
|
||||
|
||||
workflow = self.create_workflow('multi_instance_parallel')
|
||||
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
self.assertEqual(9, len(workflow_api.navigation))
|
||||
ready_items = [nav for nav in workflow_api.navigation if nav.state == "READY"]
|
||||
self.assertEqual(5, len(ready_items))
|
||||
|
||||
self.assertEqual("UserTask", workflow_api.next_task.type)
|
||||
self.assertEqual("MultiInstanceTask",workflow_api.next_task.name)
|
||||
self.assertEqual("Primary Investigator", workflow_api.next_task.title)
|
||||
|
||||
data = workflow_api.next_task.data
|
||||
data['investigator']['email'] = "dhf8r@virginia.edu"
|
||||
self.complete_form(workflow, workflow_api.next_task, data, update_all=True)
|
||||
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
self.assertEqual(WorkflowStatus.complete, workflow.status)
|
||||
data = workflow.next_task.data
|
||||
for key in data["StudyInfo"]["investigators"]:
|
||||
self.assertEquals("dhf8r@virginia.edu", data["StudyInfo"]["investigators"][key]['email'])
|
||||
|
||||
|
|
@ -256,30 +256,6 @@ class TestTasksApi(BaseTest):
|
|||
self.assertEqual("JustAValue", task.properties['JustAKey'])
|
||||
|
||||
|
||||
@patch('crc.services.protocol_builder.requests.get')
|
||||
def test_multi_instance_task(self, mock_get):
|
||||
|
||||
self.load_example_data()
|
||||
|
||||
# Enable the protocol builder.
|
||||
app.config['PB_ENABLED'] = True
|
||||
|
||||
# This depends on getting a list of investigators back from the protocol builder.
|
||||
mock_get.return_value.ok = True
|
||||
mock_get.return_value.text = self.protocol_builder_response('investigators.json')
|
||||
|
||||
workflow = self.create_workflow('multi_instance')
|
||||
|
||||
# get the first form in the two form workflow.
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
navigation = self.get_workflow_api(workflow).navigation
|
||||
self.assertEqual(5, len(navigation)) # Start task, form_task, multi_task, end task
|
||||
self.assertEqual("UserTask", workflow.next_task.type)
|
||||
self.assertEqual(MultiInstanceType.sequential.value, workflow.next_task.multi_instance_type)
|
||||
self.assertEqual(5, workflow.next_task.multi_instance_count)
|
||||
|
||||
# Assure that the names for each task are properly updated, so they aren't all the same.
|
||||
self.assertEqual("Primary Investigator", workflow.next_task.title)
|
||||
|
||||
def test_lookup_endpoint_for_task_field_enumerations(self):
|
||||
workflow = self.create_workflow('enum_options_with_search')
|
||||
|
@ -445,42 +421,3 @@ class TestTasksApi(BaseTest):
|
|||
workflow = self.get_workflow_api(workflow)
|
||||
self.assertEqual('Task_Why_No_Bananas', workflow.next_task.name)
|
||||
|
||||
@patch('crc.services.protocol_builder.requests.get')
|
||||
def test_parallel_multi_instance(self, mock_get):
|
||||
|
||||
# Assure we get nine investigators back from the API Call, as set in the investigators.json file.
|
||||
app.config['PB_ENABLED'] = True
|
||||
mock_get.return_value.ok = True
|
||||
mock_get.return_value.text = self.protocol_builder_response('investigators.json')
|
||||
|
||||
self.load_example_data()
|
||||
|
||||
workflow = self.create_workflow('multi_instance_parallel')
|
||||
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
self.assertEqual(9, len(workflow_api.navigation))
|
||||
ready_items = [nav for nav in workflow_api.navigation if nav.state == "READY"]
|
||||
self.assertEqual(5, len(ready_items))
|
||||
|
||||
self.assertEqual("UserTask", workflow_api.next_task.type)
|
||||
self.assertEqual("MultiInstanceTask",workflow_api.next_task.name)
|
||||
self.assertEqual("Primary Investigator", workflow_api.next_task.title)
|
||||
|
||||
for i in random.sample(range(5), 5):
|
||||
task_id = ready_items[i].task_id
|
||||
rv = self.app.put('/v1.0/workflow/%i/task/%s/set_token' % (workflow.id, task_id),
|
||||
headers=self.logged_in_headers(),
|
||||
content_type="application/json")
|
||||
self.assert_success(rv)
|
||||
json_data = json.loads(rv.get_data(as_text=True))
|
||||
workflow = WorkflowApiSchema().load(json_data)
|
||||
data = workflow.next_task.data
|
||||
data['investigator']['email'] = "dhf8r@virginia.edu"
|
||||
self.complete_form(workflow, workflow.next_task, data)
|
||||
#tasks = self.get_workflow_api(workflow).user_tasks
|
||||
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
self.assertEqual(WorkflowStatus.complete, workflow.status)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@ class TestBooleanDefault(BaseTest):
|
|||
def do_test(self, yes_no):
|
||||
workflow = self.create_workflow('boolean_default_value')
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
first_task = workflow_api.next_task
|
||||
result = self.complete_form(workflow_api, first_task, {'yes_no': yes_no})
|
||||
set_default_task = workflow_api.next_task
|
||||
result = self.complete_form(workflow_api, set_default_task, {'yes_no': yes_no})
|
||||
return result
|
||||
|
||||
def test_boolean_true_string(self):
|
||||
|
|
|
@ -98,3 +98,19 @@ class TestWorkflowService(BaseTest):
|
|||
def test_expressions_in_forms(self):
|
||||
workflow_spec_model = self.load_test_spec("form_expressions")
|
||||
WorkflowService.test_spec(workflow_spec_model.id)
|
||||
|
||||
def test_set_value(self):
|
||||
destiation = {}
|
||||
path = "a.b.c"
|
||||
value = "abracadara"
|
||||
result = WorkflowService.set_dot_value(path, value, destiation)
|
||||
self.assertEqual(value, destiation["a"]["b"]["c"])
|
||||
|
||||
def test_get_dot_value(self):
|
||||
path = "a.b.c"
|
||||
source = {"a":{"b":{"c" : "abracadara"}}, "a.b.c":"garbage"}
|
||||
result = WorkflowService.get_dot_value(path, source)
|
||||
self.assertEqual("abracadara", result)
|
||||
|
||||
result2 = WorkflowService.get_dot_value(path, {"a.b.c":"garbage"})
|
||||
self.assertEqual("garbage", result2)
|
||||
|
|
Loading…
Reference in New Issue