From 033502425e483d41b11136e0c11187cda328f06b Mon Sep 17 00:00:00 2001 From: Kevin Burnett <18027+burnettk@users.noreply.github.com> Date: Tue, 20 Jun 2023 07:37:49 -0700 Subject: [PATCH] Feature/better subworkflow management part 2 (#341) * Revert "Revert "Feature/better subworkflow management (#331)"" This reverts commit 3becffc14f03f79f31582ea2b7f1610eed0e71bc. * updated SpiffWorkflow to fix infinite loop in task trace w/ burnettk --------- Co-authored-by: burnettk Co-authored-by: jasquat --- .../bin/last_pi_milestone_query | 32 +++++++++++++++++++ spiffworkflow-backend/poetry.lock | 2 +- spiffworkflow-backend/pyproject.toml | 1 - .../services/message_service.py | 8 ++++- .../services/process_instance_processor.py | 5 +-- .../services/task_service.py | 16 +++++----- .../services/workflow_execution_service.py | 1 - .../message_receiver.bpmn | 8 ++--- .../message_sender.bpmn | 8 ++--- .../unit/test_error_handling_service.py | 2 +- .../cypress/e2e/process_models.cy.js | 3 ++ .../src/routes/ProcessModelShow.tsx | 1 + 12 files changed, 62 insertions(+), 25 deletions(-) create mode 100755 spiffworkflow-backend/bin/last_pi_milestone_query diff --git a/spiffworkflow-backend/bin/last_pi_milestone_query b/spiffworkflow-backend/bin/last_pi_milestone_query new file mode 100755 index 000000000..89841fdd7 --- /dev/null +++ b/spiffworkflow-backend/bin/last_pi_milestone_query @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +function error_handler() { + >&2 echo "Exited with BAD EXIT CODE '${2}' in ${0} script at line: ${1}." + exit "$2" +} +trap 'error_handler ${LINENO} $?' ERR +set -o errtrace -o errexit -o nounset -o pipefail + +mysql -uroot spiffworkflow_backend_local_development -e ' + SELECT td.bpmn_identifier FROM process_instance p + + JOIN process_instance_event pie ON pie.process_instance_id = p.id + JOIN task t ON t.guid = pie.task_guid + JOIN task_definition td ON td.id = t.task_definition_id + + JOIN ( + SELECT max(pie.id) as max_pie_id + FROM process_instance_event pie + + JOIN task t ON t.guid = pie.task_guid + JOIN task_definition td ON td.id = t.task_definition_id + JOIN bpmn_process bp ON bp.id = t.bpmn_process_id + + WHERE td.typename = "IntermediateThrowEvent" OR (bp.direct_parent_process_id is NULL AND td.typename IN ("SimpleBpmnTask", "BpmnStartTask")) + + GROUP BY pie.process_instance_id + ) AS max_pie ON max_pie.max_pie_id = pie.id + + WHERE pie.process_instance_id = 27 + ; +' diff --git a/spiffworkflow-backend/poetry.lock b/spiffworkflow-backend/poetry.lock index 89a7a459d..14808545f 100644 --- a/spiffworkflow-backend/poetry.lock +++ b/spiffworkflow-backend/poetry.lock @@ -2385,7 +2385,7 @@ lxml = "*" type = "git" url = "https://github.com/sartography/SpiffWorkflow" reference = "main" -resolved_reference = "efcdcf545c861d58cfae92ad070b3208ef6028db" +resolved_reference = "442535ae5ef892766ae476aee97b0cf3fdc833db" [[package]] name = "sqlalchemy" diff --git a/spiffworkflow-backend/pyproject.toml b/spiffworkflow-backend/pyproject.toml index 3b231070e..341249752 100644 --- a/spiffworkflow-backend/pyproject.toml +++ b/spiffworkflow-backend/pyproject.toml @@ -30,7 +30,6 @@ flask-restful = "*" flask-simple-crypt = "^0.3.3" werkzeug = "*" SpiffWorkflow = {git = "https://github.com/sartography/SpiffWorkflow", rev = "main"} -# SpiffWorkflow = {git = "https://github.com/sartography/SpiffWorkflow", rev = "6cad2981712bb61eca23af1adfafce02d3277cb9"} # SpiffWorkflow = {develop = true, path = "../../spiffworkflow/" } sentry-sdk = "^1.10" # sphinx-autoapi = "^2.0" diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/message_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/message_service.py index e73144da3..9b17176e6 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/message_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/message_service.py @@ -1,3 +1,4 @@ +from SpiffWorkflow.bpmn.workflow import BpmnMessage # type: ignore from spiffworkflow_backend.models.db import db from spiffworkflow_backend.models.message_instance import MessageInstanceModel from spiffworkflow_backend.models.message_instance import MessageStatuses @@ -143,8 +144,13 @@ class MessageService: message_model_name: str, message_payload: dict, ) -> None: + bpmn_message = BpmnMessage( + None, + message_model_name, + message_payload, + ) processor_receive = ProcessInstanceProcessor(process_instance_receive) - processor_receive.bpmn_process_instance.catch_bpmn_message(message_model_name, message_payload) + processor_receive.bpmn_process_instance.catch_bpmn_message(bpmn_message) processor_receive.do_engine_steps(save=True) message_instance_receive.status = MessageStatuses.completed.value db.session.add(message_instance_receive) 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 17ef28777..8a79d20a9 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -1018,7 +1018,7 @@ class ProcessInstanceProcessor: # in the xml, it's the id attribute. this identifies the process where the activity lives. # if it's in a subprocess, it's the inner process. - bpmn_process_identifier = ready_or_waiting_task.workflow.name + bpmn_process_identifier = ready_or_waiting_task.workflow.spec.name form_file_name = None ui_form_file_name = None @@ -1081,9 +1081,6 @@ class ProcessInstanceProcessor: event_definition = self._event_serializer.registry.restore(event_data) if payload is not None: event_definition.payload = payload - current_app.logger.info( - f"Event of type {event_definition.event_type} sent to process instance {self.process_instance_model.id}" - ) try: self.bpmn_process_instance.catch(event_definition) except Exception as e: diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py index 63ae9e2c6..c670635a5 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py @@ -157,7 +157,7 @@ class TaskService: """ (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( + spiff_task_of_parent_subprocess = spiff_task.workflow.top_workflow.get_task_from_id( UUID(parent_subprocess_guid) ) @@ -242,11 +242,11 @@ class TaskService: self.bpmn_processes[bpmn_process.guid or "top_level"] = bpmn_process - if spiff_workflow.outer_workflow != spiff_workflow: + if spiff_workflow.parent_task_id: direct_parent_bpmn_process = BpmnProcessModel.query.filter_by( id=bpmn_process.direct_parent_process_id ).first() - self.update_bpmn_process(spiff_workflow.outer_workflow, direct_parent_bpmn_process) + self.update_bpmn_process(spiff_workflow.parent_workflow, direct_parent_bpmn_process) def update_task_model( self, @@ -311,7 +311,7 @@ class TaskService: # This is the top level workflow, which has no guid # check for bpmn_process_id because mypy doesn't realize bpmn_process can be None if self.process_instance.bpmn_process_id is None: - spiff_workflow = spiff_task.workflow._get_outermost_workflow() + spiff_workflow = spiff_task.workflow.top_workflow bpmn_process = self.add_bpmn_process( bpmn_process_dict=self.serializer.workflow_to_dict(spiff_workflow), spiff_workflow=spiff_workflow, @@ -321,7 +321,7 @@ class TaskService: if bpmn_process is None: spiff_workflow = spiff_task.workflow bpmn_process = self.add_bpmn_process( - bpmn_process_dict=self.serializer.workflow_to_dict(subprocess), + bpmn_process_dict=self.serializer.subworkflow_to_dict(subprocess), top_level_process=self.process_instance.bpmn_process, bpmn_process_guid=subprocess_guid, spiff_workflow=spiff_workflow, @@ -369,10 +369,10 @@ class TaskService: bpmn_process.bpmn_process_definition = bpmn_process_definition if top_level_process is not None: - subprocesses = spiff_workflow._get_outermost_workflow().subprocesses + subprocesses = spiff_workflow.top_workflow.subprocesses direct_bpmn_process_parent = top_level_process for subprocess_guid, subprocess in subprocesses.items(): - if subprocess == spiff_workflow.outer_workflow: + if subprocess == spiff_workflow.parent_workflow: direct_bpmn_process_parent = BpmnProcessModel.query.filter_by( guid=str(subprocess_guid) ).first() @@ -674,7 +674,7 @@ class TaskService: @classmethod def _task_subprocess(cls, spiff_task: SpiffTask) -> tuple[str | None, BpmnWorkflow | None]: - top_level_workflow = spiff_task.workflow._get_outermost_workflow() + top_level_workflow = spiff_task.workflow.top_workflow my_wf = spiff_task.workflow # This is the workflow the spiff_task is part of my_sp = None my_sp_id = 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 9b1014130..2327306cc 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/workflow_execution_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/workflow_execution_service.py @@ -451,7 +451,6 @@ class WorkflowExecutionService: def queue_waiting_receive_messages(self) -> None: waiting_events = self.bpmn_process_instance.waiting_events() waiting_message_events = filter(lambda e: e["event_type"] == "MessageEventDefinition", waiting_events) - for event in waiting_message_events: # Ensure we are only creating one message instance for each waiting message if ( diff --git a/spiffworkflow-backend/tests/data/message_send_one_conversation/message_receiver.bpmn b/spiffworkflow-backend/tests/data/message_send_one_conversation/message_receiver.bpmn index 53eadc415..371fa0315 100644 --- a/spiffworkflow-backend/tests/data/message_send_one_conversation/message_receiver.bpmn +++ b/spiffworkflow-backend/tests/data/message_send_one_conversation/message_receiver.bpmn @@ -12,18 +12,18 @@ - customer_id + customer_id - customer_id + customer_id - po_number + po_number - po_number + po_number diff --git a/spiffworkflow-backend/tests/data/message_send_one_conversation/message_sender.bpmn b/spiffworkflow-backend/tests/data/message_send_one_conversation/message_sender.bpmn index e62517f72..c0911ea6c 100644 --- a/spiffworkflow-backend/tests/data/message_send_one_conversation/message_sender.bpmn +++ b/spiffworkflow-backend/tests/data/message_send_one_conversation/message_sender.bpmn @@ -18,18 +18,18 @@ It will fire a message connected to the invoice keys above, starting another pro - po_number + po_number - po_number + po_number - customer_id + customer_id - customer_id + customer_id diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_error_handling_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_error_handling_service.py index 831346840..34f263734 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_error_handling_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_error_handling_service.py @@ -1,7 +1,7 @@ import pytest from flask import Flask from flask.testing import FlaskClient -from spiffworkflow_backend import db +from spiffworkflow_backend.models.db import db from spiffworkflow_backend.models.message_instance import MessageInstanceModel from spiffworkflow_backend.models.process_instance import ProcessInstanceModel from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus diff --git a/spiffworkflow-frontend/cypress/e2e/process_models.cy.js b/spiffworkflow-frontend/cypress/e2e/process_models.cy.js index 2801db301..5e3e0be30 100644 --- a/spiffworkflow-frontend/cypress/e2e/process_models.cy.js +++ b/spiffworkflow-frontend/cypress/e2e/process_models.cy.js @@ -74,6 +74,7 @@ describe('process-models', () => { cy.contains(`${jsonFileName}.json`).should('not.exist'); // add new bpmn file + cy.getBySel('process-model-add-file').click(); cy.contains('New BPMN File').click(); cy.contains(/^Process Model File$/); cy.get('g[data-element-id=StartEvent_1]').click(); @@ -92,6 +93,7 @@ describe('process-models', () => { cy.contains(`${bpmnFileName}.bpmn`).should('exist'); // add new dmn file + cy.getBySel('process-model-add-file').click(); cy.contains('New DMN File').click(); cy.contains(/^Process Model File$/); cy.get('g[data-element-id=decision_1]').click(); @@ -109,6 +111,7 @@ describe('process-models', () => { cy.contains(`${dmnFileName}.dmn`).should('exist'); // add new json file + cy.getBySel('process-model-add-file').click(); cy.contains('New JSON File').click(); cy.contains(/^Process Model File$/); // Some reason, cypress evals json strings so we have to escape it it with '{{}' diff --git a/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx b/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx index 2f24916db..f51c5085e 100644 --- a/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx @@ -526,6 +526,7 @@ export default function ProcessModelShow() { size="lg" label="Add File" type="inline" + data-qa="process-model-add-file" onChange={(a: any) => { if (a.selectedItem.text === 'New BPMN File') { navigate(