Merge pull request #86 from sartography/feature/call_actitity

Feature/call actitity
This commit is contained in:
Kevin Burnett 2022-09-09 15:48:08 +00:00 committed by GitHub
commit 79bd9a5853
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 646 additions and 64 deletions

View File

@ -1,8 +1,8 @@
"""empty message
Revision ID: e16389841ca6
Revision ID: 240bdce32a9f
Revises:
Create Date: 2022-09-07 11:41:16.981763
Create Date: 2022-09-08 12:49:51.609196
"""
from alembic import op
@ -10,7 +10,7 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'e16389841ca6'
revision = '240bdce32a9f'
down_revision = None
branch_labels = None
depends_on = None
@ -25,6 +25,13 @@ def upgrade():
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('token')
)
op.create_table('bpmn_process_id_lookup',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('bpmn_process_identifier', sa.String(length=50), nullable=True),
sa.Column('bpmn_file_relative_path', sa.String(length=255), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_bpmn_process_id_lookup_bpmn_process_identifier'), 'bpmn_process_id_lookup', ['bpmn_process_identifier'], unique=True)
op.create_table('group',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=255), nullable=True),
@ -290,5 +297,7 @@ def downgrade():
op.drop_index(op.f('ix_message_model_identifier'), table_name='message_model')
op.drop_table('message_model')
op.drop_table('group')
op.drop_index(op.f('ix_bpmn_process_id_lookup_bpmn_process_identifier'), table_name='bpmn_process_id_lookup')
op.drop_table('bpmn_process_id_lookup')
op.drop_table('admin_session')
# ### end Alembic commands ###

4
poetry.lock generated
View File

@ -1847,7 +1847,7 @@ pytz = "*"
type = "git"
url = "https://github.com/sartography/SpiffWorkflow"
reference = "main"
resolved_reference = "776b2724255a51f9cb9497ff922768fdc80b03da"
resolved_reference = "ecd01f20a0d7142115f4ac66ccef341fcf2b176f"
[[package]]
name = "sqlalchemy"
@ -2157,7 +2157,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
content-hash = "3fe7680583608ddd4ea93082b0c6c250507b53b2211218e00b2df01649bd6c6b"
content-hash = "7a3c07a2eef00685adbf44b6e26b740e20fc52bf85e916b6c171b13d4fcc6dc9"
[metadata.files]
alabaster = [

View File

@ -27,9 +27,9 @@ flask-marshmallow = "*"
flask-migrate = "*"
flask-restful = "*"
werkzeug = "*"
spiffworkflow = {git = "https://github.com/sartography/SpiffWorkflow", rev = "main"}
# spiffworkflow = {git = "https://github.com/sartography/SpiffWorkflow", rev = "feature/centralized-logging"}
# spiffworkflow = {develop = true, path = "/home/jason/projects/github/sartography/SpiffWorkflow"}
SpiffWorkflow = {git = "https://github.com/sartography/SpiffWorkflow", rev = "main"}
# SpiffWorkflow = {git = "https://github.com/sartography/SpiffWorkflow", rev = "feature/dependencies"}
# SpiffWorkflow = {develop = true, path = "/home/jason/projects/github/sartography/SpiffWorkflow"}
sentry-sdk = "1.9.0"
sphinx-autoapi = "^1.8.4"
# flask-bpmn = {develop = true, path = "/home/jason/projects/github/sartography/flask-bpmn"}

View File

@ -30,3 +30,7 @@ OPEN_ID_REALM_NAME = environ.get("OPEN_ID_REALM_NAME", default="spiffworkflow")
OPEN_ID_CLIENT_SECRET_KEY = environ.get(
"OPEN_ID_CLIENT_SECRET_KEY", default="JXeQExm0JhQPLumgHtIIqf52bDalHz0q"
) # noqa: S105
SPIFFWORKFLOW_BACKEND_LOG_TO_FILE = (
environ.get("SPIFFWORKFLOW_BACKEND_LOG_TO_FILE", default="false") == "false"
)

View File

@ -1,4 +1,9 @@
"""Testing.py."""
from os import environ
TESTING = True
SECRET_KEY = "the_secret_key"
SPIFFWORKFLOW_BACKEND_LOG_TO_FILE = (
environ.get("SPIFFWORKFLOW_BACKEND_LOG_TO_FILE", default="true") == "true"
)

View File

@ -5,6 +5,9 @@ autoflake8 will remove these lines without the noqa comment
from flask_bpmn.models.db import add_listeners
from spiffworkflow_backend.models.active_task import ActiveTaskModel # noqa: F401
from spiffworkflow_backend.models.bpmn_process_id_lookup import (
BpmnProcessIdLookup,
) # noqa: F401
from spiffworkflow_backend.models.data_store import DataStoreModel # noqa: F401
from spiffworkflow_backend.models.file import FileModel # noqa: F401
from spiffworkflow_backend.models.message_correlation_property import (

View File

@ -0,0 +1,13 @@
"""Message_model."""
from flask_bpmn.models.db import db
from flask_bpmn.models.db import SpiffworkflowBaseDBModel
class BpmnProcessIdLookup(SpiffworkflowBaseDBModel):
"""BpmnProcessIdLookup."""
__tablename__ = "bpmn_process_id_lookup"
id = db.Column(db.Integer, primary_key=True)
bpmn_process_identifier = db.Column(db.String(50), unique=True, index=True)
bpmn_file_relative_path = db.Column(db.String(255))

View File

@ -277,7 +277,9 @@ def add_file(process_group_id: str, process_model_id: str) -> flask.wrappers.Res
file.process_model_id = process_model.id
file.process_group_id = process_model.process_group_id
if not process_model.primary_process_id and file.type == FileType.bpmn.value:
SpecFileService.set_primary_bpmn(process_model, file.name)
SpecFileService.process_bpmn_file(
process_model, file.name, set_primary_file=True
)
process_model_service.save_process_model(process_model)
return Response(
json.dumps(FileSchema().dump(file)), status=201, mimetype="application/json"

View File

@ -48,6 +48,21 @@ class FileSystemService:
FileSystemService.root_path(), FileSystemService.LIBRARY_SPECS, name
)
@staticmethod
def full_path_from_relative_path(relative_path: str) -> str:
"""Full_path_from_relative_path."""
return os.path.join(FileSystemService.root_path(), relative_path)
@staticmethod
def process_model_relative_path(spec: ProcessModelInfo) -> str:
"""Get the file path to a process model relative to BPMN_SPEC_ABSOLUTE_DIR.
If the full path is /path/to/process-group-a/group-b/process-model-a, it will return:
process-group-a/group-b/process-model-a
"""
workflow_path = FileSystemService.workflow_path(spec)
return os.path.relpath(workflow_path, start=FileSystemService.root_path())
@staticmethod
def process_group_path_for_spec(spec: ProcessModelInfo) -> str:
"""Category_path_for_spec."""

View File

@ -109,11 +109,6 @@ def setup_logger(app: Flask) -> None:
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
log_level = logging.DEBUG
log_formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
# the json formatter is nice for real environments but makes
# debugging locally a little more difficult
if app.env != "development":
@ -131,25 +126,31 @@ def setup_logger(app: Flask) -> None:
)
log_formatter = json_formatter
spiff_logger_filehandler = None
if app.config["SPIFFWORKFLOW_BACKEND_LOG_TO_FILE"]:
spiff_logger_filehandler = logging.FileHandler(f"log/{app.env}.log")
spiff_logger_filehandler.setLevel(logging.DEBUG)
spiff_logger_filehandler.setFormatter(log_formatter)
# make all loggers act the same
for name in logging.root.manager.loggerDict:
if "spiff" not in name:
the_logger = logging.getLogger(name)
the_logger.setLevel(log_level)
for the_handler in the_logger.handlers:
the_handler.setFormatter(log_formatter)
the_handler.setLevel(log_level)
if spiff_logger_filehandler:
the_logger.handlers = []
the_logger.propagate = False
the_logger.addHandler(spiff_logger_filehandler)
else:
for the_handler in the_logger.handlers:
the_handler.setFormatter(log_formatter)
the_handler.setLevel(log_level)
spiff_logger = logging.getLogger("spiff")
spiff_logger.setLevel(logging.DEBUG)
# spiff_logger_handler = logging.StreamHandler(sys.stdout)
spiff_formatter = logging.Formatter(
"%(asctime)s | %(levelname)s | %(message)s | %(action)s | %(task_type)s | %(process)s | %(processName)s | %(process_instance_id)s"
)
# spiff_logger_handler.setFormatter(spiff_formatter)
# fh = logging.FileHandler('test.log')
# spiff_logger_handler.setLevel(logging.DEBUG)
# spiff_logger.addHandler(spiff_logger_handler)
# if you add a handler to spiff, it will be used/inherited by spiff.metrics
# if you add a filter to the spiff logger directly (and not the handler), it will NOT be inherited by spiff.metrics

View File

@ -1,5 +1,6 @@
"""Process_instance_processor."""
import json
import os
import time
from typing import Any
from typing import Dict
@ -47,6 +48,7 @@ from SpiffWorkflow.spiff.serializer import UserTaskConverter
from SpiffWorkflow.util.deep_merge import DeepMerge # type: ignore
from spiffworkflow_backend.models.active_task import ActiveTaskModel
from spiffworkflow_backend.models.bpmn_process_id_lookup import BpmnProcessIdLookup
from spiffworkflow_backend.models.file import File
from spiffworkflow_backend.models.file import FileType
from spiffworkflow_backend.models.message_correlation import MessageCorrelationModel
@ -65,6 +67,7 @@ from spiffworkflow_backend.models.process_model import ProcessModelInfo
from spiffworkflow_backend.models.task_event import TaskAction
from spiffworkflow_backend.models.task_event import TaskEventModel
from spiffworkflow_backend.models.user import UserModelSchema
from spiffworkflow_backend.services.file_system_service import FileSystemService
from spiffworkflow_backend.services.process_model_service import ProcessModelService
from spiffworkflow_backend.services.spec_file_service import SpecFileService
from spiffworkflow_backend.services.user_service import UserService
@ -449,6 +452,42 @@ class ProcessInstanceProcessor:
parser = MyCustomParser()
return parser
@staticmethod
def find_required_files(
bpmn_file_full_path: str,
parser: BpmnDmnParser,
processed_identifiers: Optional[set[str]] = None,
) -> None:
"""Find_required_files."""
if processed_identifiers is None:
processed_identifiers = set()
parser.get_dependencies()
processor_dependencies = parser.get_process_dependencies()
processor_dependencies_new = processor_dependencies - processed_identifiers
new_bpmn_files = set()
for bpmn_process_identifier in processor_dependencies_new:
bpmn_process_id_lookup = BpmnProcessIdLookup.query.filter_by(
bpmn_process_identifier=bpmn_process_identifier
).first()
new_bpmn_file_full_path = None
if bpmn_process_id_lookup is None:
# TODO: this should only happen rarely
new_bpmn_file_full_path = ""
else:
new_bpmn_file_full_path = os.path.join(
FileSystemService.root_path(),
bpmn_process_id_lookup.bpmn_file_relative_path,
)
new_bpmn_files.add(new_bpmn_file_full_path)
processed_identifiers.add(bpmn_process_identifier)
for new_bpmn_file_full_path in new_bpmn_files:
parser.add_bpmn_file(new_bpmn_file_full_path)
ProcessInstanceProcessor.find_required_files(
new_bpmn_file_full_path, parser, processed_identifiers
)
@staticmethod
def get_spec(
files: List[File], process_model_info: ProcessModelInfo
@ -475,6 +514,13 @@ class ProcessInstanceProcessor:
% process_model_info.id,
)
)
workflow_path = FileSystemService.workflow_path(process_model_info) or ""
primary_file_full_path = os.path.join(
workflow_path, (process_model_info.primary_file_name or "")
)
ProcessInstanceProcessor.find_required_files(primary_file_full_path, parser)
try:
spec = parser.get_spec(process_model_info.primary_process_id)

View File

@ -12,6 +12,7 @@ from lxml.etree import _Element # type: ignore
from lxml.etree import Element as EtreeElement
from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException # type: ignore
from spiffworkflow_backend.models.bpmn_process_id_lookup import BpmnProcessIdLookup
from spiffworkflow_backend.models.file import File
from spiffworkflow_backend.models.file import FileType
from spiffworkflow_backend.models.message_correlation_property import (
@ -72,13 +73,21 @@ class SpecFileService(FileSystemService):
file_path = SpecFileService.file_path(process_model_info, file_name)
SpecFileService.write_file_data_to_system(file_path, binary_data)
file = SpecFileService.to_file_object(file_name, file_path)
if file_name == process_model_info.primary_file_name:
SpecFileService.set_primary_bpmn(process_model_info, file_name, binary_data)
elif process_model_info.primary_file_name is None and file.type == str(
FileType.bpmn
):
# If no primary process exists, make this pirmary process.
SpecFileService.set_primary_bpmn(process_model_info, file_name, binary_data)
if file.type == str(FileType.bpmn):
set_primary_file = False
if (
process_model_info.primary_file_name is None
or file_name == process_model_info.primary_file_name
):
# If no primary process exists, make this primary process.
set_primary_file = True
SpecFileService.process_bpmn_file(
process_model_info,
file_name,
binary_data,
set_primary_file=set_primary_file,
)
return file
@ -138,10 +147,11 @@ class SpecFileService(FileSystemService):
shutil.rmtree(dir_path)
@staticmethod
def set_primary_bpmn(
def process_bpmn_file(
process_model_info: ProcessModelInfo,
file_name: str,
binary_data: Optional[bytes] = None,
set_primary_file: Optional[bool] = False,
) -> None:
"""Set_primary_bpmn."""
# If this is a BPMN, extract the process id, and determine if it is contains swim lanes.
@ -151,13 +161,23 @@ class SpecFileService(FileSystemService):
if not binary_data:
binary_data = SpecFileService.get_data(process_model_info, file_name)
try:
bpmn: EtreeElement = etree.fromstring(binary_data)
process_model_info.primary_process_id = SpecFileService.get_process_id(
bpmn
bpmn_etree_element: EtreeElement = etree.fromstring(binary_data)
if set_primary_file:
process_model_info.primary_process_id = (
SpecFileService.get_process_id(bpmn_etree_element)
)
process_model_info.primary_file_name = file_name
process_model_info.is_review = SpecFileService.has_swimlane(
bpmn_etree_element
)
SpecFileService.check_for_message_models(
bpmn_etree_element, process_model_info
)
SpecFileService.store_process_ids(
process_model_info, file_name, bpmn_etree_element
)
process_model_info.primary_file_name = file_name
process_model_info.is_review = SpecFileService.has_swimlane(bpmn)
SpecFileService.check_for_message_models(bpmn, process_model_info)
except etree.XMLSyntaxError as xse:
raise ApiError(
@ -197,8 +217,8 @@ class SpecFileService(FileSystemService):
return retval
@staticmethod
def get_process_id(et_root: _Element) -> str:
"""Get_process_id."""
def get_executable_process_elements(et_root: _Element) -> list[_Element]:
"""Get_executable_process_elements."""
process_elements = []
for child in et_root:
if child.tag.endswith("process") and child.attrib.get(
@ -208,6 +228,19 @@ class SpecFileService(FileSystemService):
if len(process_elements) == 0:
raise ValidationException("No executable process tag found")
return process_elements
@staticmethod
def get_executable_process_ids(et_root: _Element) -> list[str]:
"""Get_executable_process_ids."""
process_elements = SpecFileService.get_executable_process_elements(et_root)
process_ids = [pe.attrib["id"] for pe in process_elements]
return process_ids
@staticmethod
def get_process_id(et_root: _Element) -> str:
"""Get_process_id."""
process_elements = SpecFileService.get_executable_process_elements(et_root)
# There are multiple root elements
if len(process_elements) > 1:
@ -226,6 +259,48 @@ class SpecFileService(FileSystemService):
return str(process_elements[0].attrib["id"])
@staticmethod
def store_process_ids(
process_model_info: ProcessModelInfo, bpmn_file_name: str, et_root: _Element
) -> None:
"""Store_process_ids."""
relative_process_model_path = SpecFileService.process_model_relative_path(
process_model_info
)
relative_bpmn_file_path = os.path.join(
relative_process_model_path, bpmn_file_name
)
process_ids = SpecFileService.get_executable_process_ids(et_root)
for process_id in process_ids:
process_id_lookup = BpmnProcessIdLookup.query.filter_by(
bpmn_process_identifier=process_id
).first()
if process_id_lookup is None:
process_id_lookup = BpmnProcessIdLookup(
bpmn_process_identifier=process_id,
bpmn_file_relative_path=relative_bpmn_file_path,
)
db.session.add(process_id_lookup)
db.session.commit()
else:
if relative_bpmn_file_path != process_id_lookup.bpmn_file_relative_path:
full_bpmn_file_path = SpecFileService.full_path_from_relative_path(
process_id_lookup.bpmn_file_relative_path
)
# if the old relative bpmn file no longer exists, then assume things were moved around
# on the file system. Otherwise, assume it is a duplicate process id and error.
if os.path.isfile(full_bpmn_file_path):
raise ValidationException(
f"Process id ({process_id}) has already been used for "
f"{process_id_lookup.bpmn_file_relative_path}. It cannot be reused."
)
else:
process_id_lookup.bpmn_file_relative_path = (
relative_bpmn_file_path
)
db.session.add(process_id_lookup)
db.session.commit()
@staticmethod
def check_for_message_models(
et_root: _Element, process_model_info: ProcessModelInfo

View File

@ -0,0 +1,55 @@
<?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" id="Definitions_f07329e" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0">
<bpmn:process id="Level2" name="Level 2" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1g3dpd7</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1g3dpd7" sourceRef="StartEvent_1" targetRef="level3" />
<bpmn:callActivity id="level3" name="call level 3" calledElement="Level3">
<bpmn:incoming>Flow_1g3dpd7</bpmn:incoming>
<bpmn:outgoing>Flow_0qdgvah</bpmn:outgoing>
</bpmn:callActivity>
<bpmn:sequenceFlow id="Flow_0qdgvah" sourceRef="level3" targetRef="level2b_second_call" />
<bpmn:endEvent id="Event_18dla68">
<bpmn:documentation># Main Workflow
Hello {{my_other_var}}
</bpmn:documentation>
<bpmn:incoming>Flow_1ll6j9j</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1ll6j9j" sourceRef="level2b_second_call" targetRef="Event_18dla68" />
<bpmn:callActivity id="level2b_second_call" name="call level 2b again" calledElement="Level2b">
<bpmn:incoming>Flow_0qdgvah</bpmn:incoming>
<bpmn:outgoing>Flow_1ll6j9j</bpmn:outgoing>
</bpmn:callActivity>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Level2">
<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_0mcej1g_di" bpmnElement="level3">
<dc:Bounds x="280" y="77" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_18dla68_di" bpmnElement="Event_18dla68">
<dc:Bounds x="552" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0522m2j_di" bpmnElement="level2b_second_call">
<dc:Bounds x="430" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1g3dpd7_di" bpmnElement="Flow_1g3dpd7">
<di:waypoint x="215" y="117" />
<di:waypoint x="280" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0qdgvah_di" bpmnElement="Flow_0qdgvah">
<di:waypoint x="380" y="117" />
<di:waypoint x="430" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1ll6j9j_di" bpmnElement="Flow_1ll6j9j">
<di:waypoint x="530" y="117" />
<di:waypoint x="552" y="117" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,44 @@
<?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" id="Definitions_f07329e" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0">
<bpmn:process id="Level2b" name="Level 2b" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1g3dpd7</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1g3dpd7" sourceRef="StartEvent_1" targetRef="do_nothing" />
<bpmn:endEvent id="Event_18dla68">
<bpmn:documentation># Main Workflow
Hello {{my_other_var}}
</bpmn:documentation>
<bpmn:incoming>Flow_0l0w6u9</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0l0w6u9" sourceRef="do_nothing" targetRef="Event_18dla68" />
<bpmn:scriptTask id="do_nothing" name="Do Nothing">
<bpmn:incoming>Flow_1g3dpd7</bpmn:incoming>
<bpmn:outgoing>Flow_0l0w6u9</bpmn:outgoing>
<bpmn:script>a = 1</bpmn:script>
</bpmn:scriptTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Level2b">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_18dla68_di" bpmnElement="Event_18dla68">
<dc:Bounds x="432" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1reqred_di" bpmnElement="do_nothing">
<dc:Bounds x="260" y="77" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1g3dpd7_di" bpmnElement="Flow_1g3dpd7">
<di:waypoint x="215" y="117" />
<di:waypoint x="260" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0l0w6u9_di" bpmnElement="Flow_0l0w6u9">
<di:waypoint x="360" y="117" />
<di:waypoint x="432" y="117" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,43 @@
<?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" id="Definitions_f07329e" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0">
<bpmn:process id="Level3" name="Level 3" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1g3dpd7</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1g3dpd7" sourceRef="StartEvent_1" targetRef="do_nothing" />
<bpmn:sequenceFlow id="Flow_0qdgvah" sourceRef="do_nothing" targetRef="Event_18dla68" />
<bpmn:endEvent id="Event_18dla68">
<bpmn:documentation># Main Workflow
Hello {{my_other_var}}
</bpmn:documentation>
<bpmn:incoming>Flow_0qdgvah</bpmn:incoming>
</bpmn:endEvent>
<bpmn:scriptTask id="do_nothing" name="Do Nothing">
<bpmn:incoming>Flow_1g3dpd7</bpmn:incoming>
<bpmn:outgoing>Flow_0qdgvah</bpmn:outgoing>
<bpmn:script>a = 3</bpmn:script>
</bpmn:scriptTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Level3">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_18dla68_di" bpmnElement="Event_18dla68">
<dc:Bounds x="432" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1po21cu_di" bpmnElement="do_nothing">
<dc:Bounds x="280" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1g3dpd7_di" bpmnElement="Flow_1g3dpd7">
<di:waypoint x="215" y="117" />
<di:waypoint x="280" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0qdgvah_di" bpmnElement="Flow_0qdgvah">
<di:waypoint x="380" y="117" />
<di:waypoint x="432" y="117" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,55 @@
<?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:camunda="http://camunda.org/schema/1.0/bpmn" id="Definitions_f07329e" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0">
<bpmn:process id="Level1" name="Level1" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1g3dpd7</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1g3dpd7" sourceRef="StartEvent_1" targetRef="Activity_12zat0d" />
<bpmn:callActivity id="Activity_12zat0d" name="call level 2" calledElement="Level2">
<bpmn:incoming>Flow_1g3dpd7</bpmn:incoming>
<bpmn:outgoing>Flow_0qdgvah</bpmn:outgoing>
</bpmn:callActivity>
<bpmn:sequenceFlow id="Flow_0qdgvah" sourceRef="Activity_12zat0d" targetRef="Activity_0rkbhbz" />
<bpmn:endEvent id="Event_18dla68">
<bpmn:documentation># Main Workflow
Hello {{my_other_var}}
</bpmn:documentation>
<bpmn:incoming>Flow_0upce00</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0upce00" sourceRef="Activity_0rkbhbz" targetRef="Event_18dla68" />
<bpmn:callActivity id="Activity_0rkbhbz" name="call level 2B" calledElement="Level2b">
<bpmn:incoming>Flow_0qdgvah</bpmn:incoming>
<bpmn:outgoing>Flow_0upce00</bpmn:outgoing>
</bpmn:callActivity>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Level1">
<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_0mcej1g_di" bpmnElement="Activity_12zat0d">
<dc:Bounds x="280" y="77" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_18dla68_di" bpmnElement="Event_18dla68">
<dc:Bounds x="702" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0jddvat_di" bpmnElement="Activity_0rkbhbz">
<dc:Bounds x="420" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1g3dpd7_di" bpmnElement="Flow_1g3dpd7">
<di:waypoint x="215" y="117" />
<di:waypoint x="280" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0qdgvah_di" bpmnElement="Flow_0qdgvah">
<di:waypoint x="380" y="117" />
<di:waypoint x="420" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0upce00_di" bpmnElement="Flow_0upce00">
<di:waypoint x="520" y="117" />
<di:waypoint x="702" y="117" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,72 @@
<?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"
xmlns:camunda="http://camunda.org/schema/1.0/bpmn" id="Definitions_f07329e" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0">
<bpmn:process id="Level1" name="Level1" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1g3dpd7</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1g3dpd7" sourceRef="StartEvent_1" targetRef="Activity_12zat0d" />
<bpmn:callActivity id="Activity_12zat0d" name="call level 2" calledElement="Level2">
<bpmn:incoming>Flow_1g3dpd7</bpmn:incoming>
<bpmn:outgoing>Flow_0qdgvah</bpmn:outgoing>
</bpmn:callActivity>
<bpmn:sequenceFlow id="Flow_0qdgvah" sourceRef="Activity_12zat0d" targetRef="Activity_0rkbhbz" />
<bpmn:endEvent id="Event_18dla68">
<bpmn:documentation># Main Workflow
Hello {{my_other_var}}
</bpmn:documentation>
<bpmn:incoming>Flow_04o2npf</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0upce00" sourceRef="Activity_0rkbhbz" targetRef="Activity_0gg4414" />
<bpmn:callActivity id="Activity_0rkbhbz" name="call level 2B" calledElement="Level2b">
<bpmn:incoming>Flow_0qdgvah</bpmn:incoming>
<bpmn:outgoing>Flow_0upce00</bpmn:outgoing>
</bpmn:callActivity>
<bpmn:sequenceFlow id="Flow_04o2npf" sourceRef="Activity_0gg4414" targetRef="Event_18dla68" />
<bpmn:businessRuleTask id="Activity_0gg4414" name="call level 2c" camunda:decisionRef="Level2c">
<bpmn:extensionElements>
<spiffworkflow:calledDecisionId>Level2c</spiffworkflow:calledDecisionId>
</bpmn:extensionElements>
<bpmn:incoming>Flow_0upce00</bpmn:incoming>
<bpmn:outgoing>Flow_04o2npf</bpmn:outgoing>
</bpmn:businessRuleTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Level1">
<bpmndi:BPMNEdge id="Flow_0qdgvah_di" bpmnElement="Flow_0qdgvah">
<di:waypoint x="380" y="117" />
<di:waypoint x="420" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1g3dpd7_di" bpmnElement="Flow_1g3dpd7">
<di:waypoint x="215" y="117" />
<di:waypoint x="280" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0upce00_di" bpmnElement="Flow_0upce00">
<di:waypoint x="520" y="117" />
<di:waypoint x="560" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_04o2npf_di" bpmnElement="Flow_04o2npf">
<di:waypoint x="660" y="117" />
<di:waypoint x="702" 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_0mcej1g_di" bpmnElement="Activity_12zat0d">
<dc:Bounds x="280" y="77" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0jddvat_di" bpmnElement="Activity_0rkbhbz">
<dc:Bounds x="420" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_18dla68_di" bpmnElement="Event_18dla68">
<dc:Bounds x="702" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1lzid7b_di" bpmnElement="Activity_0gg4414">
<dc:Bounds x="560" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/" xmlns:dmndi="https://www.omg.org/spec/DMN/20191111/DMNDI/" xmlns:dc="http://www.omg.org/spec/DMN/20180521/DC/" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Level2c" name="DRD" namespace="http://camunda.org/schema/1.0/dmn" exporter="Camunda Modeler" exporterVersion="5.0.0" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.0.0">
<decision id="Decision_0vrtcmk" name="Decision 1">
<decisionTable id="DecisionTable_1gamsry">
<input id="Input_1">
<inputExpression id="InputExpression_1" typeRef="string">
<text></text>
</inputExpression>
</input>
<output id="Output_1" typeRef="string" />
</decisionTable>
</decision>
<dmndi:DMNDI>
<dmndi:DMNDiagram>
<dmndi:DMNShape dmnElementRef="Decision_0vrtcmk">
<dc:Bounds height="80" width="180" x="160" y="100" />
</dmndi:DMNShape>
</dmndi:DMNDiagram>
</dmndi:DMNDI>
</definitions>

View File

@ -1,7 +1,7 @@
<?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:collaboration id="Collaboration_0oye1os">
<bpmn:participant id="message_receiver_one" name="Message Receiver" processRef="message_receiver_process" />
<bpmn:participant id="message_receiver_one" name="Message Receiver" processRef="message_receiver_process_one" />
<bpmn:participant id="message_sender" name="Message Sender" />
<bpmn:messageFlow id="message_send_flow" name="Message Send Flow" sourceRef="message_sender" targetRef="receive_message" />
<bpmn:messageFlow id="Flow_0ds946g" sourceRef="send_message_response" targetRef="message_sender" />
@ -40,7 +40,7 @@
}</spiffworkflow:messagePayload>
</bpmn:extensionElements>
</bpmn:message>
<bpmn:process id="message_receiver_process" name="Message Receiver Process" isExecutable="true">
<bpmn:process id="message_receiver_process_one" name="Message Receiver Process" isExecutable="true">
<bpmn:sequenceFlow id="Flow_11r9uiw" sourceRef="send_message_response" targetRef="Event_0q5otqd" />
<bpmn:endEvent id="Event_0q5otqd">
<bpmn:incoming>Flow_11r9uiw</bpmn:incoming>

View File

@ -1,7 +1,7 @@
<?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:collaboration id="Collaboration_0oye1os">
<bpmn:participant id="message_receiver_two" name="Message Receiver" processRef="message_receiver_process" />
<bpmn:participant id="message_receiver_two" name="Message Receiver" processRef="message_receiver_process_two" />
<bpmn:participant id="message_sender" name="Message Sender" />
<bpmn:messageFlow id="message_send_flow" name="Message Send Flow" sourceRef="message_sender" targetRef="receive_message" />
<bpmn:messageFlow id="Flow_0ds946g" sourceRef="send_message_response" targetRef="message_sender" />
@ -40,7 +40,7 @@
}</spiffworkflow:messagePayload>
</bpmn:extensionElements>
</bpmn:message>
<bpmn:process id="message_receiver_process" name="Message Receiver Process" isExecutable="true">
<bpmn:process id="message_receiver_process_two" name="Message Receiver Process" isExecutable="true">
<bpmn:sequenceFlow id="Flow_11r9uiw" sourceRef="send_message_response" targetRef="Event_0q5otqd" />
<bpmn:endEvent id="Event_0q5otqd">
<bpmn:incoming>Flow_11r9uiw</bpmn:incoming>

View File

@ -216,7 +216,7 @@ class BaseTest:
# return public_access_token
def create_process_instance_from_process_model(
self, process_model: ProcessModelInfo, status: str
self, process_model: ProcessModelInfo, status: Optional[str] = "not_started"
) -> ProcessInstanceModel:
"""Create_process_instance_from_process_model."""
user = self.find_or_create_user()

View File

@ -90,6 +90,7 @@ class ExampleDataLoader:
continue # Don't try to process sub directories
filename = os.path.basename(file_path)
# since there are multiple bpmn files in a test data directory, ensure we set the correct one as the primary
is_primary = filename.lower() == bpmn_file_name_with_extension
file = None
try:
@ -99,12 +100,11 @@ class ExampleDataLoader:
process_model_info=spec, file_name=filename, binary_data=data
)
if is_primary:
SpecFileService.set_primary_bpmn(spec, filename, data)
SpecFileService.process_bpmn_file(
spec, filename, data, set_primary_file=True
)
workflow_spec_service = ProcessModelService()
workflow_spec_service.save_process_model(spec)
except IsADirectoryError:
# Ignore sub directories
pass
finally:
if file:
file.close()

View File

@ -1,20 +1,56 @@
"""Process Model."""
from flask.app import Flask
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.process_model import ProcessModelInfo
from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor,
)
def test_initializes_files_as_empty_array() -> None:
"""Test_initializes_files_as_empty_array."""
process_model_one = create_test_process_model(
id="model_one", display_name="Model One"
)
assert process_model_one.files == []
assert process_model_one.libraries == []
class TestProcessModel(BaseTest):
"""TestProcessModel."""
def test_initializes_files_as_empty_array(self) -> None:
"""Test_initializes_files_as_empty_array."""
process_model_one = self.create_test_process_model(
id="model_one", display_name="Model One"
)
assert process_model_one.files == []
assert process_model_one.libraries == []
def create_test_process_model(id: str, display_name: str) -> ProcessModelInfo:
"""Create_test_process_model."""
return ProcessModelInfo(
id=id,
display_name=display_name,
description=display_name,
)
def test_can_run_process_model_with_call_activities(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test_can_run_process_model_with_call_activities."""
process_model = load_test_spec(
"call_activity_nested",
process_model_source_directory="call_activity_nested",
bpmn_file_name="call_activity_nested",
)
bpmn_file_names = [
"call_activity_level_2b",
"call_activity_level_2",
"call_activity_level_3",
]
for bpmn_file_name in bpmn_file_names:
load_test_spec(
bpmn_file_name,
process_model_source_directory="call_activity_nested",
bpmn_file_name=bpmn_file_name,
)
process_instance = self.create_process_instance_from_process_model(
process_model
)
processor = ProcessInstanceProcessor(process_instance)
processor.do_engine_steps()
def create_test_process_model(self, id: str, display_name: str) -> ProcessModelInfo:
"""Create_test_process_model."""
return ProcessModelInfo(
id=id,
display_name=display_name,
description=display_name,
)

View File

@ -1,13 +1,97 @@
"""Test_message_service."""
import os
import pytest
from flask import Flask
from flask_bpmn.api.api_error import ApiError
from flask_bpmn.models.db import db
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.bpmn_process_id_lookup import BpmnProcessIdLookup
class TestSpecFileService(BaseTest):
"""TestSpecFileService."""
def test_can_check_for_messages_in_bpmn_xml(
call_activity_nested_relative_file_path = os.path.join(
"test_process_group_id", "call_activity_nested", "call_activity_nested.bpmn"
)
def test_can_store_process_ids_for_lookup(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test_can_check_for_messages_in_bpmn_xml."""
assert True
"""Test_can_store_process_ids_for_lookup."""
load_test_spec(
"call_activity_nested",
process_model_source_directory="call_activity_nested",
bpmn_file_name="call_activity_nested",
)
bpmn_process_id_lookups = BpmnProcessIdLookup.query.all()
assert len(bpmn_process_id_lookups) == 1
assert bpmn_process_id_lookups[0].bpmn_process_identifier == "Level1"
assert (
bpmn_process_id_lookups[0].bpmn_file_relative_path
== self.call_activity_nested_relative_file_path
)
def test_fails_to_save_duplicate_process_id(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test_fails_to_save_duplicate_process_id."""
bpmn_process_identifier = "Level1"
load_test_spec(
"call_activity_nested",
process_model_source_directory="call_activity_nested",
bpmn_file_name="call_activity_nested",
)
bpmn_process_id_lookups = BpmnProcessIdLookup.query.all()
assert len(bpmn_process_id_lookups) == 1
assert (
bpmn_process_id_lookups[0].bpmn_process_identifier
== bpmn_process_identifier
)
assert (
bpmn_process_id_lookups[0].bpmn_file_relative_path
== self.call_activity_nested_relative_file_path
)
with pytest.raises(ApiError) as exception:
load_test_spec(
"call_activity_nested_duplicate",
process_model_source_directory="call_activity_nested",
bpmn_file_name="call_activity_nested_duplicate",
)
assert f"Process id ({bpmn_process_identifier}) has already been used" in str(
exception.value
)
def test_updates_relative_file_path_when_appropriate(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
) -> None:
"""Test_updates_relative_file_path_when_appropriate."""
bpmn_process_identifier = "Level1"
bpmn_file_relative_path = os.path.join(
"test_process_group_id", "call_activity_nested", "new_bpmn_file.bpmn"
)
process_id_lookup = BpmnProcessIdLookup(
bpmn_process_identifier=bpmn_process_identifier,
bpmn_file_relative_path=bpmn_file_relative_path,
)
db.session.add(process_id_lookup)
db.session.commit()
load_test_spec(
"call_activity_nested",
process_model_source_directory="call_activity_nested",
bpmn_file_name="call_activity_nested",
)
bpmn_process_id_lookups = BpmnProcessIdLookup.query.all()
assert len(bpmn_process_id_lookups) == 1
assert (
bpmn_process_id_lookups[0].bpmn_process_identifier
== bpmn_process_identifier
)
assert (
bpmn_process_id_lookups[0].bpmn_file_relative_path
== self.call_activity_nested_relative_file_path
)