added test for loopback to subprocess and fixed issue w/ burnettk

This commit is contained in:
jasquat 2023-03-31 10:48:16 -04:00
parent ac730e57a9
commit fefac239e2
5 changed files with 180 additions and 17 deletions

View File

@ -1897,6 +1897,11 @@ class ProcessInstanceProcessor:
all_tasks = self.bpmn_process_instance.get_tasks(TaskState.ANY_MASK) all_tasks = self.bpmn_process_instance.get_tasks(TaskState.ANY_MASK)
return [t for t in all_tasks if t.state in [TaskState.WAITING, TaskState.READY]] return [t for t in all_tasks if t.state in [TaskState.WAITING, TaskState.READY]]
def get_task_by_guid(
self, task_guid: str
) -> Optional[SpiffTask]:
return self.bpmn_process_instance.get_task_from_id(UUID(task_guid))
@classmethod @classmethod
def get_task_by_bpmn_identifier( def get_task_by_bpmn_identifier(
cls, bpmn_task_identifier: str, bpmn_process_instance: BpmnWorkflow cls, bpmn_task_identifier: str, bpmn_process_instance: BpmnWorkflow

View File

@ -60,7 +60,7 @@ class TaskService:
spiff_task: SpiffTask, spiff_task: SpiffTask,
) -> None: ) -> None:
self.process_spiff_task_children(spiff_task) self.process_spiff_task_children(spiff_task)
self.process_spiff_task_parents(spiff_task) self.process_spiff_task_parent_subprocess_tasks(spiff_task)
self.save_objects_to_database() self.save_objects_to_database()
def process_spiff_task_children( def process_spiff_task_children(
@ -68,9 +68,9 @@ class TaskService:
spiff_task: SpiffTask, spiff_task: SpiffTask,
) -> None: ) -> None:
for child_spiff_task in spiff_task.children: for child_spiff_task in spiff_task.children:
if child_spiff_task._has_state(TaskState.PREDICTED_MASK): # if child_spiff_task._has_state(TaskState.PREDICTED_MASK):
self.__class__.remove_spiff_task_from_parent(child_spiff_task, self.task_models) # self.__class__.remove_spiff_task_from_parent(child_spiff_task, self.task_models)
continue # continue
self.update_task_model_with_spiff_task( self.update_task_model_with_spiff_task(
spiff_task=child_spiff_task, spiff_task=child_spiff_task,
) )
@ -78,10 +78,15 @@ class TaskService:
spiff_task=child_spiff_task, spiff_task=child_spiff_task,
) )
def process_spiff_task_parents( def process_spiff_task_parent_subprocess_tasks(
self, self,
spiff_task: SpiffTask, spiff_task: SpiffTask,
) -> None: ) -> None:
"""Find the parent subprocess of a given spiff_task and update its data.
This will also process that subprocess task's children and will recurse upwards
to process its parent subprocesses as well.
"""
(parent_subprocess_guid, _parent_subprocess) = self.__class__.task_subprocess(spiff_task) (parent_subprocess_guid, _parent_subprocess) = self.__class__.task_subprocess(spiff_task)
if parent_subprocess_guid is not None: if parent_subprocess_guid is not None:
spiff_task_of_parent_subprocess = spiff_task.workflow._get_outermost_workflow().get_task_from_id( spiff_task_of_parent_subprocess = spiff_task.workflow._get_outermost_workflow().get_task_from_id(
@ -92,7 +97,10 @@ class TaskService:
self.update_task_model_with_spiff_task( self.update_task_model_with_spiff_task(
spiff_task=spiff_task_of_parent_subprocess, spiff_task=spiff_task_of_parent_subprocess,
) )
self.process_spiff_task_parents( self.process_spiff_task_children(
spiff_task=spiff_task_of_parent_subprocess,
)
self.process_spiff_task_parent_subprocess_tasks(
spiff_task=spiff_task_of_parent_subprocess, spiff_task=spiff_task_of_parent_subprocess,
) )
@ -391,9 +399,9 @@ class TaskService:
# we are going to avoid saving likely and maybe tasks to the db. # we are going to avoid saving likely and maybe tasks to the db.
# that means we need to remove them from their parents' lists of children as well. # that means we need to remove them from their parents' lists of children as well.
spiff_task = spiff_workflow.get_task_from_id(UUID(task_id)) spiff_task = spiff_workflow.get_task_from_id(UUID(task_id))
if spiff_task._has_state(TaskState.PREDICTED_MASK): # if spiff_task._has_state(TaskState.PREDICTED_MASK):
cls.remove_spiff_task_from_parent(spiff_task, new_task_models) # cls.remove_spiff_task_from_parent(spiff_task, new_task_models)
continue # continue
task_model = TaskModel.query.filter_by(guid=task_id).first() task_model = TaskModel.query.filter_by(guid=task_id).first()
if task_model is None: if task_model is None:

View File

@ -110,8 +110,8 @@ class TaskModelSavingDelegate(EngineStepDelegate):
# ): # ):
# self._update_task_model_with_spiff_task(waiting_spiff_task) # self._update_task_model_with_spiff_task(waiting_spiff_task)
if self.last_completed_spiff_task is not None: if self.last_completed_spiff_task is not None:
import pdb; pdb.set_trace() # import pdb; pdb.set_trace()
self.task_service.process_spiff_task_parents(self.last_completed_spiff_task) self.task_service.process_spiff_task_parent_subprocess_tasks(self.last_completed_spiff_task)
self.task_service.process_spiff_task_children(self.last_completed_spiff_task) self.task_service.process_spiff_task_children(self.last_completed_spiff_task)
def _should_update_task_model(self) -> bool: def _should_update_task_model(self) -> bool:

View File

@ -0,0 +1,116 @@
<?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" xmlns:spiffworkflow="http://spiffworkflow.org/bpmn/schema/1.0/core" id="Definitions_96f6665" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
<bpmn:process id="test_loopback_to_subprocess" isExecutable="true">
<bpmn:startEvent id="Event_17ujsfj">
<bpmn:outgoing>Flow_1dk6oyl</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:exclusiveGateway id="gateway" name="Gateway" default="Flow_11uu31d">
<bpmn:incoming>Flow_0s9lss3</bpmn:incoming>
<bpmn:outgoing>Flow_02xy1ag</bpmn:outgoing>
<bpmn:outgoing>Flow_11uu31d</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:sequenceFlow id="Flow_0s9lss3" sourceRef="script_task" targetRef="gateway" />
<bpmn:scriptTask id="script_task" name="Script Task">
<bpmn:incoming>Flow_0sw85uk</bpmn:incoming>
<bpmn:outgoing>Flow_0s9lss3</bpmn:outgoing>
<bpmn:script>x=1</bpmn:script>
</bpmn:scriptTask>
<bpmn:endEvent id="Event_0ryttlc">
<bpmn:incoming>Flow_02xy1ag</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_02xy1ag" sourceRef="gateway" targetRef="Event_0ryttlc">
<bpmn:conditionExpression>x==2</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<bpmn:sequenceFlow id="Flow_1dk6oyl" sourceRef="Event_17ujsfj" targetRef="subprocess" />
<bpmn:sequenceFlow id="Flow_0sw85uk" sourceRef="subprocess" targetRef="script_task" />
<bpmn:subProcess id="subprocess" name="Subprocess">
<bpmn:incoming>Flow_1dk6oyl</bpmn:incoming>
<bpmn:incoming>Flow_11uu31d</bpmn:incoming>
<bpmn:outgoing>Flow_0sw85uk</bpmn:outgoing>
<bpmn:startEvent id="Event_17df4es">
<bpmn:outgoing>Flow_0ih1i19</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_0ih1i19" sourceRef="Event_17df4es" targetRef="subprocess_manual_task" />
<bpmn:endEvent id="Event_1ehwj0c">
<bpmn:incoming>Flow_0dua5j8</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0dua5j8" sourceRef="subprocess_manual_task" targetRef="Event_1ehwj0c" />
<bpmn:manualTask id="subprocess_manual_task" name="Subprocess Manual Task">
<bpmn:extensionElements>
<spiffworkflow:instructionsForEndUser>HEY MANUAL</spiffworkflow:instructionsForEndUser>
</bpmn:extensionElements>
<bpmn:incoming>Flow_0ih1i19</bpmn:incoming>
<bpmn:outgoing>Flow_0dua5j8</bpmn:outgoing>
</bpmn:manualTask>
</bpmn:subProcess>
<bpmn:sequenceFlow id="Flow_11uu31d" sourceRef="gateway" targetRef="subprocess" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="test_loopback_to_subprocess">
<bpmndi:BPMNShape id="Event_17ujsfj_di" bpmnElement="Event_17ujsfj">
<dc:Bounds x="122" y="-168" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_16ouwyf_di" bpmnElement="gateway" isMarkerVisible="true">
<dc:Bounds x="565" y="-175" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="568" y="-118" width="44" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1tvzm43_di" bpmnElement="script_task">
<dc:Bounds x="370" y="-190" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0ryttlc_di" bpmnElement="Event_0ryttlc">
<dc:Bounds x="712" y="-168" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_10og25a_di" bpmnElement="subprocess">
<dc:Bounds x="210" y="-190" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0s9lss3_di" bpmnElement="Flow_0s9lss3">
<di:waypoint x="470" y="-150" />
<di:waypoint x="565" y="-150" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_02xy1ag_di" bpmnElement="Flow_02xy1ag">
<di:waypoint x="615" y="-150" />
<di:waypoint x="712" y="-150" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1dk6oyl_di" bpmnElement="Flow_1dk6oyl">
<di:waypoint x="158" y="-150" />
<di:waypoint x="210" y="-150" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0sw85uk_di" bpmnElement="Flow_0sw85uk">
<di:waypoint x="310" y="-150" />
<di:waypoint x="370" y="-150" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_11uu31d_di" bpmnElement="Flow_11uu31d">
<di:waypoint x="590" y="-175" />
<di:waypoint x="590" y="-250" />
<di:waypoint x="438" y="-250" />
<di:waypoint x="303" y="-189" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
<bpmndi:BPMNDiagram id="BPMNDiagram_0d2d8pf">
<bpmndi:BPMNPlane id="BPMNPlane_0ez33hq" bpmnElement="subprocess">
<bpmndi:BPMNShape id="Event_17df4es_di" bpmnElement="Event_17df4es">
<dc:Bounds x="212" y="172" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1ehwj0c_di" bpmnElement="Event_1ehwj0c">
<dc:Bounds x="452" y="172" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0va03mf_di" bpmnElement="subprocess_manual_task">
<dc:Bounds x="300" y="150" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0ih1i19_di" bpmnElement="Flow_0ih1i19">
<di:waypoint x="248" y="190" />
<di:waypoint x="300" y="190" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0dua5j8_di" bpmnElement="Flow_0dua5j8">
<di:waypoint x="400" y="190" />
<di:waypoint x="452" y="190" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -696,14 +696,48 @@ class TestProcessInstanceProcessor(BaseTest):
assert len(process_instance.human_tasks) == 2 assert len(process_instance.human_tasks) == 2
human_task_two = process_instance.active_human_tasks[0] human_task_two = process_instance.active_human_tasks[0]
# this is just asserting the way the functionality currently works in spiff.
# we would actually expect this to change one day if we stop reusing the same guid
# when we re-do a task.
# assert human_task_two.task_id == human_task_one.task_id
# EDIT: when using feature/remove-loop-reset branch of SpiffWorkflow, these should be different.
assert human_task_two.task_id != human_task_one.task_id assert human_task_two.task_id != human_task_one.task_id
def test_it_can_loopback_to_previous_bpmn_subprocess_with_gateway(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
) -> None:
initiator_user = self.find_or_create_user("initiator_user")
process_model = load_test_spec(
process_model_id="test_group/loopback_to_subprocess",
process_model_source_directory="loopback_to_subprocess",
)
process_instance = self.create_process_instance_from_process_model(
process_model=process_model, user=initiator_user
)
processor = ProcessInstanceProcessor(process_instance)
processor.do_engine_steps(save=True)
assert len(process_instance.active_human_tasks) == 1
assert len(process_instance.human_tasks) == 1
human_task_one = process_instance.active_human_tasks[0]
spiff_task = processor.get_task_by_guid(human_task_one.task_id)
ProcessInstanceService.complete_form_task(processor, spiff_task, {}, initiator_user, human_task_one)
processor = ProcessInstanceProcessor(process_instance)
assert len(process_instance.active_human_tasks) == 1
assert len(process_instance.human_tasks) == 2
human_task_two = process_instance.active_human_tasks[0]
spiff_task = processor.get_task_by_guid(human_task_two.task_id)
ProcessInstanceService.complete_form_task(processor, spiff_task, {}, initiator_user, human_task_two)
import pdb; pdb.set_trace()
# ensure this does not raise a KeyError
processor = ProcessInstanceProcessor(process_instance)
assert len(process_instance.active_human_tasks) == 1
assert len(process_instance.human_tasks) == 3
human_task_three = process_instance.active_human_tasks[0]
spiff_task = processor.get_task_by_guid(human_task_three.task_id)
ProcessInstanceService.complete_form_task(processor, spiff_task, {}, initiator_user, human_task_three)
def test_task_data_is_set_even_if_process_instance_errors( def test_task_data_is_set_even_if_process_instance_errors(
self, self,
app: Flask, app: Flask,