STG-26 - basic test case for a looping task

Criteria :
task.multi_instance_type == 'looping'

to terminate, use the standard endpoint for submitting form data with a query variable of terminate_loop=true

Will likely need two buttons:
"Submit and quit"
"Submit and add another"
or something similar
This commit is contained in:
Kelly McDonald 2020-06-17 11:35:06 -04:00
parent 7723862530
commit 1844c93919
6 changed files with 131 additions and 11 deletions

View File

@ -626,6 +626,12 @@ paths:
schema:
type: string
format: uuid
- name: terminate_loop
in: query
required: false
description: Terminate the loop on a looping task
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.

View File

@ -175,7 +175,7 @@ def set_current_task(workflow_id, task_id):
return WorkflowApiSchema().dump(workflow_api_model)
def update_task(workflow_id, task_id, body):
def update_task(workflow_id, task_id, body, terminate_loop=None):
workflow_model = session.query(WorkflowModel).filter_by(id=workflow_id).first()
if workflow_model is None:
@ -191,6 +191,9 @@ def update_task(workflow_id, task_id, body):
if task.state != task.READY:
raise ApiError("invalid_state", "You may not update a task unless it is in the READY state. "
"Consider calling a token reset to make this task Ready.")
if terminate_loop:
task.terminate_loop()
task.update_data(body)
processor.complete_task(task)
processor.do_engine_steps()

View File

@ -290,7 +290,7 @@ class BaseTest(unittest.TestCase):
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, user_uid="dhf8r"):
def complete_form(self, workflow_in, task_in, dict_data, 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"]
@ -299,11 +299,16 @@ class BaseTest(unittest.TestCase):
user = session.query(UserModel).filter_by(uid=user_uid).first()
self.assertIsNotNone(user)
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))
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))
if error_code:
self.assert_failure(rv, error_code=error_code)
return
@ -316,7 +321,9 @@ class BaseTest(unittest.TestCase):
# The total number of tasks may change over time, as users move through gateways
# branches may be pruned. As we hit parallel Multi-Instance new tasks may be created...
self.assertIsNotNone(workflow.total_tasks)
self.assertEqual(prev_completed_task_count + 1, workflow.completed_tasks)
# presumably, we also need to deal with sequential items here too . .
if not task_in.multi_instance_type == 'looping':
self.assertEqual(prev_completed_task_count + 1, workflow.completed_tasks)
# Assure a record exists in the Task Events
task_events = session.query(TaskEventModel) \
@ -335,7 +342,8 @@ class BaseTest(unittest.TestCase):
self.assertEqual(task_in.name, event.task_name)
self.assertEqual(task_in.title, event.task_title)
self.assertEqual(task_in.type, event.task_type)
self.assertEqual("COMPLETED", event.task_state)
if not task_in.multi_instance_type == 'looping':
self.assertEqual("COMPLETED", event.task_state)
# Not sure what voodoo is happening inside of marshmallow to get me in this state.
if isinstance(task_in.multi_instance_type, MultiInstanceType):
@ -344,7 +352,10 @@ class BaseTest(unittest.TestCase):
self.assertEqual(task_in.multi_instance_type, event.mi_type)
self.assertEqual(task_in.multi_instance_count, event.mi_count)
self.assertEqual(task_in.multi_instance_index, event.mi_index)
if task_in.multi_instance_type == 'looping' and not terminate_loop:
self.assertEqual(task_in.multi_instance_index+1, event.mi_index)
else:
self.assertEqual(task_in.multi_instance_index, event.mi_index)
self.assertEqual(task_in.process_name, event.process_name)
self.assertIsNotNone(event.date)

View File

@ -0,0 +1,45 @@
<?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_1v9xfjq" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.0">
<bpmn:process id="looping_task" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_0vlor2k</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:userTask id="GetNames" name="Get Names" camunda:formKey="GetNamesForm">
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="GetNames_MICurrentVar.Name" type="string" />
<camunda:formField id="GetNames_MICurrentVar.Nickname" type="string" />
</camunda:formData>
</bpmn:extensionElements>
<bpmn:incoming>Flow_0vlor2k</bpmn:incoming>
<bpmn:outgoing>Flow_1tvod7v</bpmn:outgoing>
<bpmn:standardLoopCharacteristics />
</bpmn:userTask>
<bpmn:endEvent id="Event_End">
<bpmn:incoming>Flow_1tvod7v</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1tvod7v" sourceRef="GetNames" targetRef="Event_End" />
<bpmn:sequenceFlow id="Flow_0vlor2k" sourceRef="StartEvent_1" targetRef="GetNames" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="looping_task">
<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_1mpvzb7_di" bpmnElement="GetNames">
<dc:Bounds x="250" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0dhdik5_di" bpmnElement="Event_End">
<dc:Bounds x="402" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1tvod7v_di" bpmnElement="Flow_1tvod7v">
<di:waypoint x="350" y="117" />
<di:waypoint x="402" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0vlor2k_di" bpmnElement="Flow_0vlor2k">
<di:waypoint x="215" y="117" />
<di:waypoint x="250" y="117" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,54 @@
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 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):
"""Tests the Workflow Processor as it deals with a Looping task"""
def _populate_form_with_random_data(self, task):
api_task = WorkflowService.spiff_task_to_api_task(task, add_docs_and_forms=True)
WorkflowService.populate_form_with_random_data(task, api_task, required_only=False)
def get_processor(self, study_model, spec_model):
workflow_model = StudyService._create_workflow_model(study_model, spec_model)
return WorkflowProcessor(workflow_model)
def test_create_and_complete_workflow(self):
# This depends on getting a list of investigators back from the protocol builder.
workflow = self.create_workflow('looping_task')
task = self.get_workflow_api(workflow).next_task
self.assertEqual("GetNames", task.name)
self.assertEqual(task.multi_instance_type, 'looping')
self.assertEqual(1, task.multi_instance_index)
self.complete_form(workflow,task,{'GetNames_MICurrentVar':{'Name': 'Peter Norvig', 'Nickname': 'Pete'}})
task = self.get_workflow_api(workflow).next_task
self.assertEqual(task.multi_instance_type,'looping')
self.assertEqual(2, task.multi_instance_index)
self.complete_form(workflow,
task,
{'GetNames_MICurrentVar':{'Name': 'Stuart Russell', 'Nickname': 'Stu'}},
terminate_loop=True)
task = self.get_workflow_api(workflow).next_task
self.assertEqual(task.name,'Event_End')
self.assertEqual(workflow.completed_tasks,workflow.total_tasks)
self.assertEqual(task.data, {'GetNames_MICurrentVar': 2,
'GetNames_MIData': {'1': {'Name': 'Peter Norvig',
'Nickname': 'Pete'},
'2': {'Name': 'Stuart Russell',
'Nickname': 'Stu'}}})

View File

@ -32,7 +32,8 @@ class TestWorkflowProcessorMultiInstance(BaseTest):
'error': 'Unable to locate a user with id asd3v in LDAP'}}
def _populate_form_with_random_data(self, task):
WorkflowProcessor.populate_form_with_random_data(task)
WorkflowService.populate_form_with_random_data(task)
def get_processor(self, study_model, spec_model):
workflow_model = StudyService._create_workflow_model(study_model, spec_model)