diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py index 1fff87c1..a5bab8af 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -1897,6 +1897,11 @@ class ProcessInstanceProcessor: 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]] + def get_task_by_guid( + self, task_guid: str + ) -> Optional[SpiffTask]: + return self.bpmn_process_instance.get_task_from_id(UUID(task_guid)) + @classmethod def get_task_by_bpmn_identifier( cls, bpmn_task_identifier: str, bpmn_process_instance: BpmnWorkflow diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py index fef8265f..e9839fa7 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py @@ -60,7 +60,7 @@ class TaskService: spiff_task: SpiffTask, ) -> None: 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() def process_spiff_task_children( @@ -68,9 +68,9 @@ class TaskService: spiff_task: SpiffTask, ) -> None: for child_spiff_task in spiff_task.children: - if child_spiff_task._has_state(TaskState.PREDICTED_MASK): - self.__class__.remove_spiff_task_from_parent(child_spiff_task, self.task_models) - continue + # if child_spiff_task._has_state(TaskState.PREDICTED_MASK): + # self.__class__.remove_spiff_task_from_parent(child_spiff_task, self.task_models) + # continue self.update_task_model_with_spiff_task( spiff_task=child_spiff_task, ) @@ -78,10 +78,15 @@ class TaskService: spiff_task=child_spiff_task, ) - def process_spiff_task_parents( + def process_spiff_task_parent_subprocess_tasks( self, spiff_task: SpiffTask, ) -> 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) if parent_subprocess_guid is not None: 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( 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, ) @@ -391,9 +399,9 @@ class TaskService: # 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. spiff_task = spiff_workflow.get_task_from_id(UUID(task_id)) - if spiff_task._has_state(TaskState.PREDICTED_MASK): - cls.remove_spiff_task_from_parent(spiff_task, new_task_models) - continue + # if spiff_task._has_state(TaskState.PREDICTED_MASK): + # cls.remove_spiff_task_from_parent(spiff_task, new_task_models) + # continue task_model = TaskModel.query.filter_by(guid=task_id).first() if task_model is None: diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/workflow_execution_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/workflow_execution_service.py index 495ab310..310286e7 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/workflow_execution_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/workflow_execution_service.py @@ -110,8 +110,8 @@ class TaskModelSavingDelegate(EngineStepDelegate): # ): # self._update_task_model_with_spiff_task(waiting_spiff_task) if self.last_completed_spiff_task is not None: - import pdb; pdb.set_trace() - self.task_service.process_spiff_task_parents(self.last_completed_spiff_task) + # import pdb; pdb.set_trace() + 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) def _should_update_task_model(self) -> bool: diff --git a/spiffworkflow-backend/tests/data/loopback_to_subprocess/loopback_to_subprocess.bpmn b/spiffworkflow-backend/tests/data/loopback_to_subprocess/loopback_to_subprocess.bpmn new file mode 100644 index 00000000..eff8cd2f --- /dev/null +++ b/spiffworkflow-backend/tests/data/loopback_to_subprocess/loopback_to_subprocess.bpmn @@ -0,0 +1,116 @@ + + + + + Flow_1dk6oyl + + + Flow_0s9lss3 + Flow_02xy1ag + Flow_11uu31d + + + + Flow_0sw85uk + Flow_0s9lss3 + x=1 + + + Flow_02xy1ag + + + x==2 + + + + + Flow_1dk6oyl + Flow_11uu31d + Flow_0sw85uk + + Flow_0ih1i19 + + + + Flow_0dua5j8 + + + + + HEY MANUAL + + Flow_0ih1i19 + Flow_0dua5j8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py index 3d949388..34c71e7c 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py @@ -696,14 +696,48 @@ class TestProcessInstanceProcessor(BaseTest): assert len(process_instance.human_tasks) == 2 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 + 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( self, app: Flask,