From ce23480872e0981ef89b8312a3c8103c8530450f Mon Sep 17 00:00:00 2001 From: Kevin Burnett <18027+burnettk@users.noreply.github.com> Date: Tue, 18 Jul 2023 10:50:07 -0700 Subject: [PATCH] Run event payloads data migration from background processor (#399) * move data migration code out of bin so it can be reused in background processor * sleep for 5 minutes and update bpmn js to pull in some fixes from elizabeth * update spiff to pull in parser update to make it act like before --------- Co-authored-by: burnettk --- .../bin/data_migrations/version_1_3.py | 208 ++---------------- .../bin/start_blocking_appscheduler.py | 15 +- spiffworkflow-backend/poetry.lock | 134 ++++++++++- .../data_migrations/version_1_3.py | 195 ++++++++++++++++ spiffworkflow-frontend/package-lock.json | 4 +- 5 files changed, 352 insertions(+), 204 deletions(-) create mode 100644 spiffworkflow-backend/src/spiffworkflow_backend/data_migrations/version_1_3.py diff --git a/spiffworkflow-backend/bin/data_migrations/version_1_3.py b/spiffworkflow-backend/bin/data_migrations/version_1_3.py index a6159213..64324447 100644 --- a/spiffworkflow-backend/bin/data_migrations/version_1_3.py +++ b/spiffworkflow-backend/bin/data_migrations/version_1_3.py @@ -1,203 +1,21 @@ -import copy -import json -import os -import uuid -from hashlib import sha256 +import time from spiffworkflow_backend import create_app -from spiffworkflow_backend.models.db import db -from spiffworkflow_backend.models.task import Task -from spiffworkflow_backend.models.task import TaskModel # noqa: F401 -from spiffworkflow_backend.models.task_definition import TaskDefinitionModel -from sqlalchemy import or_ -from sqlalchemy.orm.attributes import flag_modified +from spiffworkflow_backend.data_migrations.version_1_3 import VersionOneThree -class VersionOneThree: - """Migrates data in the database to be compatible with SpiffWorkflow at git revision ebcdde95. +def main() -> None: + app = create_app() + start_time = time.time() - Converts migration file from SpiffWorkflow to work with backend's db: - https://github.com/sartography/SpiffWorkflow/blob/main/SpiffWorkflow/bpmn/serializer/migration/version_1_3.py - """ - def run(self) -> None: - os.environ["SPIFFWORKFLOW_BACKEND_ENV"] = "local_development" - if os.environ.get("SPIFFWORKFLOW_BACKEND_BPMN_SPEC_ABSOLUTE_DIR") is None: - os.environ["SPIFFWORKFLOW_BACKEND_BPMN_SPEC_ABSOLUTE_DIR"] = "hey" - flask_env_key = "FLASK_SESSION_SECRET_KEY" - os.environ[flask_env_key] = "whatevs" - app = create_app() - with app.app_context(): - task_definitions = self.get_relevant_task_definitions() - for task_definition in task_definitions: - self.process_task_definition(task_definition) - relating_task_models = TaskModel.query.filter_by(task_definition_id=task_definition.id).all() - for task_model in relating_task_models: - self.process_task_model(task_model, task_definition) + with app.app_context(): + VersionOneThree().run() - task_definitions_with_events = TaskDefinitionModel.query.filter( - or_( - TaskDefinitionModel.typename.like("%Event%"), # type: ignore - TaskDefinitionModel.typename.in_(["SendTask", "ReceiveTask"]), # type: ignore - ) - ).all() - for tdwe in task_definitions_with_events: - self.update_event_definitions(tdwe) - - db.session.commit() - - def get_relevant_task_definitions(self) -> list[TaskDefinitionModel]: - task_definitions: list[TaskDefinitionModel] = TaskDefinitionModel.query.filter_by( - typename="_BoundaryEventParent" - ).all() - return task_definitions - - def process_task_definition(self, task_definition: TaskDefinitionModel) -> None: - task_definition.typename = "BoundaryEventSplit" - task_definition.bpmn_identifier = task_definition.bpmn_identifier.replace( - "BoundaryEventParent", "BoundaryEventSplit" - ) - - properties_json = copy.copy(task_definition.properties_json) - properties_json.pop("main_child_task_spec") - properties_json["typename"] = task_definition.typename - properties_json["name"] = task_definition.bpmn_identifier - task_definition.properties_json = properties_json - flag_modified(task_definition, "properties_json") # type: ignore - db.session.add(task_definition) - - join_properties_json = { - "name": task_definition.bpmn_identifier.replace("BoundaryEventSplit", "BoundaryEventJoin"), - "manual": False, - "bpmn_id": None, - "lookahead": 2, - "inputs": properties_json["outputs"], - "outputs": [], - "split_task": task_definition.bpmn_identifier, - "threshold": None, - "cancel": True, - "typename": "BoundaryEventJoin", - } - - join_task_definition = TaskDefinitionModel( - bpmn_process_definition_id=task_definition.bpmn_process_definition_id, - bpmn_identifier=join_properties_json["name"], - typename=join_properties_json["typename"], - properties_json=join_properties_json, - ) - db.session.add(join_task_definition) - - for parent_bpmn_identifier in properties_json["inputs"]: - parent_task_definition = TaskDefinitionModel.query.filter_by( - bpmn_identifier=parent_bpmn_identifier, - bpmn_process_definition_id=task_definition.bpmn_process_definition_id, - ).first() - parent_task_definition.properties_json["outputs"] = [ - name.replace("BoundaryEventParent", "BoundaryEventSplit") - for name in parent_task_definition.properties_json["outputs"] - ] - flag_modified(parent_task_definition, "properties_json") # type: ignore - db.session.add(parent_task_definition) - - for child_bpmn_identifier in properties_json["outputs"]: - child_task_definition = TaskDefinitionModel.query.filter_by( - bpmn_identifier=child_bpmn_identifier, - bpmn_process_definition_id=task_definition.bpmn_process_definition_id, - ).first() - child_task_definition.properties_json["outputs"].append(join_task_definition.bpmn_identifier) - child_task_definition.properties_json["inputs"] = [ - name.replace("BoundaryEventParent", "BoundaryEventSplit") - for name in child_task_definition.properties_json["inputs"] - ] - flag_modified(child_task_definition, "properties_json") # type: ignore - db.session.add(child_task_definition) - - def process_task_model(self, task_model: TaskModel, task_definition: TaskDefinitionModel) -> None: - task_model.properties_json["task_spec"] = task_definition.bpmn_identifier - flag_modified(task_model, "properties_json") # type: ignore - db.session.add(task_model) - - child_task_models = [] - all_children_completed = True - - # Ruff keeps complaining unless it's done like this - blank_json = json.dumps({}) - blank_json_data_hash = sha256(blank_json.encode("utf8")).hexdigest() - - for child_task_guid in task_model.properties_json["children"]: - child_task_model = TaskModel.query.filter_by(guid=child_task_guid).first() - if child_task_model is None: - continue - if child_task_model.state not in ["COMPLETED", "CANCELLED"]: - all_children_completed = False - child_task_models.append(child_task_model) - - for child_task_model in child_task_models: - if child_task_model.state == "CANCELLED": - # Cancelled tasks don't have children - continue - - new_task_state = None - start_in_seconds = child_task_model.start_in_seconds - end_in_seconds = None - - if child_task_model.state in ["MAYBE", "LIKELY", "FUTURE"]: - new_task_state = child_task_model.state - elif child_task_model.state in ["WAITING", "READY", "STARTED"]: - new_task_state = "FUTURE" - elif child_task_model.state == "COMPLETED": - if all_children_completed: - new_task_state = "COMPLETED" - end_in_seconds = child_task_model.end_in_seconds - else: - new_task_state = "WAITING" - elif child_task_model.state == "ERROR": - new_task_state = "WAITING" - else: - raise Exception(f"Unknown state: {child_task_model.state} for {child_task_model.guild}") - - new_task_properties_json = { - "id": str(uuid.uuid4()), - "parent": child_task_model.guid, - "children": [], - "state": Task.task_state_name_to_int(new_task_state), - "task_spec": task_definition.bpmn_identifier.replace("BoundaryEventSplit", "BoundaryEventJoin"), - "last_state_change": None, - "triggered": False, - "internal_data": {}, - } - - new_task_model = TaskModel( - guid=new_task_properties_json["id"], - bpmn_process_id=task_model.bpmn_process_id, - process_instance_id=task_model.process_instance_id, - task_definition_id=task_model.task_definition_id, - state=new_task_state, - properties_json=new_task_properties_json, - start_in_seconds=start_in_seconds, - end_in_seconds=end_in_seconds, - json_data_hash=blank_json_data_hash, - python_env_data_hash=blank_json_data_hash, - ) - db.session.add(new_task_model) - - child_task_model.properties_json["children"].append(new_task_model.guid) - flag_modified(child_task_model, "properties_json") # type: ignore - db.session.add(child_task_model) - - def update_event_definitions(self, task_definition: TaskDefinitionModel) -> None: - if "event_definition" in task_definition.properties_json: - properties_json = copy.copy(task_definition.properties_json) - properties_json["event_definition"].pop("internal", None) - properties_json["event_definition"].pop("external", None) - if "escalation_code" in properties_json["event_definition"]: - properties_json["event_definition"]["code"] = properties_json["event_definition"].pop( - "escalation_code" - ) - if "error_code" in properties_json["event_definition"]: - properties_json["event_definition"]["code"] = properties_json["event_definition"].pop("error_code") - task_definition.properties_json = properties_json - flag_modified(task_definition, "properties_json") # type: ignore - db.session.add(task_definition) + end_time = time.time() + print( + f"done running data migration from ./bin/data_migrations/version_1_3.py. took {end_time - start_time} seconds" + ) -VersionOneThree().run() +if __name__ == "__main__": + main() diff --git a/spiffworkflow-backend/bin/start_blocking_appscheduler.py b/spiffworkflow-backend/bin/start_blocking_appscheduler.py index 61b753f2..89a40b44 100755 --- a/spiffworkflow-backend/bin/start_blocking_appscheduler.py +++ b/spiffworkflow-backend/bin/start_blocking_appscheduler.py @@ -4,16 +4,29 @@ import time from apscheduler.schedulers.background import BlockingScheduler # type: ignore from spiffworkflow_backend import create_app from spiffworkflow_backend import start_scheduler +from spiffworkflow_backend.data_migrations.version_1_3 import VersionOneThree from spiffworkflow_backend.helpers.db_helper import try_to_connect def main() -> None: - """Main.""" + seconds_to_wait = 300 + print(f"sleeping for {seconds_to_wait} seconds to give the api container time to run the migration") + time.sleep(seconds_to_wait) + print("done sleeping") + + print("running data migration from background processor") app = create_app() start_time = time.time() + with app.app_context(): try_to_connect(start_time) + VersionOneThree().run() + end_time = time.time() + print( + f"done running data migration from background processor. took {end_time - start_time} seconds. starting" + " scheduler" + ) start_scheduler(app, BlockingScheduler) diff --git a/spiffworkflow-backend/poetry.lock b/spiffworkflow-backend/poetry.lock index 75d370d4..edc526ac 100644 --- a/spiffworkflow-backend/poetry.lock +++ b/spiffworkflow-backend/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. [[package]] name = "alembic" version = "1.10.3" description = "A database migration tool for SQLAlchemy." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -23,6 +24,7 @@ tz = ["python-dateutil"] name = "aniso8601" version = "9.0.1" description = "A library for parsing ISO 8601 strings." +category = "main" optional = false python-versions = "*" files = [ @@ -37,6 +39,7 @@ dev = ["black", "coverage", "isort", "pre-commit", "pyenchant", "pylint"] name = "apscheduler" version = "3.10.1" description = "In-process task scheduler with Cron-like capabilities" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -48,7 +51,7 @@ files = [ pytz = "*" setuptools = ">=0.7" six = ">=1.4.0" -tzlocal = ">=2.0,<3.dev0 || >=4.dev0" +tzlocal = ">=2.0,<3.0.0 || >=4.0.0" [package.extras] doc = ["sphinx", "sphinx-rtd-theme"] @@ -66,6 +69,7 @@ zookeeper = ["kazoo"] name = "attrs" version = "22.2.0" description = "Classes Without Boilerplate" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -84,6 +88,7 @@ tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy name = "bandit" version = "1.7.2" description = "Security oriented static analyser for python code." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -106,6 +111,7 @@ yaml = ["PyYAML"] name = "bcrypt" version = "4.0.1" description = "Modern password hashing for your software and your servers" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -140,6 +146,7 @@ typecheck = ["mypy"] name = "black" version = "22.12.0" description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -174,6 +181,7 @@ uvloop = ["uvloop (>=0.15.2)"] name = "blinker" version = "1.6.2" description = "Fast, simple object-to-object and broadcast signaling" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -185,6 +193,7 @@ files = [ name = "certifi" version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -196,6 +205,7 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." +category = "main" optional = false python-versions = "*" files = [ @@ -272,6 +282,7 @@ pycparser = "*" name = "cfgv" version = "3.3.1" description = "Validate configuration and produce human readable error messages." +category = "dev" optional = false python-versions = ">=3.6.1" files = [ @@ -283,6 +294,7 @@ files = [ name = "charset-normalizer" version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -367,6 +379,7 @@ files = [ name = "click" version = "8.1.3" description = "Composable command line interface toolkit" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -381,6 +394,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "clickclick" version = "20.10.2" description = "Click utility functions" +category = "main" optional = false python-versions = "*" files = [ @@ -396,6 +410,7 @@ PyYAML = ">=3.11" name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -407,6 +422,7 @@ files = [ name = "configparser" version = "5.3.0" description = "Updated configparser from stdlib for earlier Pythons." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -422,6 +438,7 @@ testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-chec name = "connexion" version = "2.14.1" description = "Connexion - API first applications with OpenAPI/Swagger and Flask" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -452,6 +469,7 @@ tests = ["MarkupSafe (>=0.23)", "aiohttp (>=2.3.10,<4)", "aiohttp-jinja2 (>=0.14 name = "coverage" version = "6.5.0" description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -517,6 +535,7 @@ toml = ["tomli"] name = "cryptography" version = "41.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -558,6 +577,7 @@ test-randomorder = ["pytest-randomly"] name = "dateparser" version = "1.1.8" description = "Date parsing library designed to parse dates from HTML pages" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -580,6 +600,7 @@ langdetect = ["langdetect"] name = "distlib" version = "0.3.6" description = "Distribution utilities" +category = "dev" optional = false python-versions = "*" files = [ @@ -591,6 +612,7 @@ files = [ name = "dparse" version = "0.6.2" description = "A parser for Python dependency files" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -610,6 +632,7 @@ pipenv = ["pipenv"] name = "exceptiongroup" version = "1.1.1" description = "Backport of PEP 654 (exception groups)" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -624,6 +647,7 @@ test = ["pytest (>=6)"] name = "execnet" version = "1.9.0" description = "execnet: rapid multi-Python deployment" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -638,6 +662,7 @@ testing = ["pre-commit"] name = "filelock" version = "3.11.0" description = "A platform independent file lock." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -653,6 +678,7 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.2.2)", "diff-cover (>=7.5)", "p name = "flask" version = "2.2.5" description = "A simple framework for building complex web applications." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -674,6 +700,7 @@ dotenv = ["python-dotenv"] name = "flask-admin" version = "1.6.1" description = "Simple and extensible admin interface framework for Flask" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -693,6 +720,7 @@ azure = ["azure-storage-blob"] name = "flask-bcrypt" version = "1.0.1" description = "Brcrypt hashing for Flask." +category = "main" optional = false python-versions = "*" files = [ @@ -708,6 +736,7 @@ Flask = "*" name = "flask-cors" version = "3.0.10" description = "A Flask extension adding a decorator for CORS support" +category = "main" optional = false python-versions = "*" files = [ @@ -723,6 +752,7 @@ Six = "*" name = "flask-jwt-extended" version = "4.4.4" description = "Extended JWT integration with Flask" +category = "main" optional = false python-versions = ">=3.7,<4" files = [ @@ -742,6 +772,7 @@ asymmetric-crypto = ["cryptography (>=3.3.1)"] name = "flask-mail" version = "0.9.1" description = "Flask extension for sending email" +category = "main" optional = false python-versions = "*" files = [ @@ -756,6 +787,7 @@ Flask = "*" name = "flask-marshmallow" version = "0.15.0" description = "Flask + marshmallow for beautiful APIs" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -778,6 +810,7 @@ tests = ["flask-sqlalchemy (>=3.0.0)", "marshmallow-sqlalchemy (>=0.28.2)", "moc name = "flask-migrate" version = "4.0.4" description = "SQLAlchemy database migrations for Flask applications using Alembic." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -794,6 +827,7 @@ Flask-SQLAlchemy = ">=1.0" name = "flask-restful" version = "0.3.9" description = "Simple framework for creating REST APIs" +category = "main" optional = false python-versions = "*" files = [ @@ -814,6 +848,7 @@ docs = ["sphinx"] name = "flask-simple-crypt" version = "0.3.3" description = "Flask extension based on simple-crypt that allows simple, secure encryption and decryption for Python." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -829,6 +864,7 @@ pycryptodome = "*" name = "flask-sqlalchemy" version = "3.0.3" description = "Add SQLAlchemy support to your Flask application." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -844,6 +880,7 @@ SQLAlchemy = ">=1.4.18" name = "gitdb" version = "4.0.10" description = "Git Object Database" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -858,6 +895,7 @@ smmap = ">=3.0.1,<6" name = "gitpython" version = "3.1.31" description = "GitPython is a Python library used to interact with Git repositories" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -872,6 +910,7 @@ gitdb = ">=4.0.1,<5" name = "greenlet" version = "2.0.2" description = "Lightweight in-process concurrent programming" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" files = [ @@ -945,6 +984,7 @@ test = ["objgraph", "psutil"] name = "gunicorn" version = "20.1.0" description = "WSGI HTTP Server for UNIX" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -965,6 +1005,7 @@ tornado = ["tornado (>=0.2)"] name = "identify" version = "2.5.22" description = "File identification library for Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -979,6 +1020,7 @@ license = ["ukkonen"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -990,6 +1032,7 @@ files = [ name = "inflection" version = "0.5.1" description = "A port of Ruby on Rails inflector to Python" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1001,6 +1044,7 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1012,6 +1056,7 @@ files = [ name = "itsdangerous" version = "2.1.2" description = "Safely pass data to untrusted environments and back." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1023,6 +1068,7 @@ files = [ name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1040,6 +1086,7 @@ i18n = ["Babel (>=2.7)"] name = "jsonschema" version = "4.17.3" description = "An implementation of JSON Schema validation for Python" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1059,6 +1106,7 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- name = "lxml" version = "4.9.2" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" files = [ @@ -1151,6 +1199,7 @@ source = ["Cython (>=0.29.7)"] name = "mako" version = "1.2.4" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1170,6 +1219,7 @@ testing = ["pytest"] name = "markupsafe" version = "2.1.2" description = "Safely add untrusted strings to HTML/XML markup." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1229,6 +1279,7 @@ files = [ name = "marshmallow" version = "3.19.0" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1249,6 +1300,7 @@ tests = ["pytest", "pytz", "simplejson"] name = "marshmallow-enum" version = "1.5.1" description = "Enum field for Marshmallow" +category = "main" optional = false python-versions = "*" files = [ @@ -1263,6 +1315,7 @@ marshmallow = ">=2.0.0" name = "marshmallow-sqlalchemy" version = "0.29.0" description = "SQLAlchemy integration with the marshmallow (de)serialization library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1285,6 +1338,7 @@ tests = ["pytest", "pytest-lazy-fixture (>=0.6.2)"] name = "mypy" version = "1.2.0" description = "Optional static typing for Python" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1331,6 +1385,7 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1342,6 +1397,7 @@ files = [ name = "mysqlclient" version = "2.2.0" description = "Python interface to MySQL" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1358,6 +1414,7 @@ files = [ name = "nodeenv" version = "1.7.0" description = "Node.js virtual environment builder" +category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -1372,6 +1429,7 @@ setuptools = "*" name = "packaging" version = "21.3" description = "Core utilities for Python packages" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1386,6 +1444,7 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" name = "pathspec" version = "0.11.1" description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1397,6 +1456,7 @@ files = [ name = "pbr" version = "5.11.1" description = "Python Build Reasonableness" +category = "dev" optional = false python-versions = ">=2.6" files = [ @@ -1408,6 +1468,7 @@ files = [ name = "platformdirs" version = "3.2.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1423,6 +1484,7 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.2.2)", "pytest- name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1438,6 +1500,7 @@ testing = ["pytest", "pytest-benchmark"] name = "pre-commit" version = "2.21.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1456,6 +1519,7 @@ virtualenv = ">=20.10.0" name = "pre-commit-hooks" version = "4.4.0" description = "Some out-of-the-box hooks for pre-commit." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1471,6 +1535,7 @@ tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} name = "prometheus-client" version = "0.16.0" description = "Python client for the Prometheus monitoring system." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1485,6 +1550,7 @@ twisted = ["twisted"] name = "prometheus-flask-exporter" version = "0.22.3" description = "Prometheus metrics exporter for Flask" +category = "main" optional = false python-versions = "*" files = [ @@ -1500,6 +1566,7 @@ prometheus-client = "*" name = "psycopg2" version = "2.9.6" description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1522,6 +1589,7 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1533,6 +1601,7 @@ files = [ name = "pycryptodome" version = "3.17" description = "Cryptographic library for Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1575,6 +1644,7 @@ files = [ name = "pygments" version = "2.15.1" description = "Pygments is a syntax highlighting package written in Python." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1589,6 +1659,7 @@ plugins = ["importlib-metadata"] name = "pyjwt" version = "2.6.0" description = "JSON Web Token implementation in Python" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1606,6 +1677,7 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] name = "pyparsing" version = "3.0.9" description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "main" optional = false python-versions = ">=3.6.8" files = [ @@ -1620,6 +1692,7 @@ diagrams = ["jinja2", "railroad-diagrams"] name = "pyrsistent" version = "0.19.3" description = "Persistent/Functional/Immutable data structures" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1656,6 +1729,7 @@ files = [ name = "pytest" version = "7.2.2" description = "pytest: simple powerful testing with Python" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1679,6 +1753,7 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2. name = "pytest-flask" version = "1.2.0" description = "A set of py.test fixtures to test Flask applications." +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1698,6 +1773,7 @@ docs = ["Sphinx", "sphinx-rtd-theme"] name = "pytest-flask-sqlalchemy" version = "1.1.0" description = "A pytest plugin for preserving test isolation in Flask-SQlAlchemy using database transactions." +category = "main" optional = false python-versions = "*" files = [ @@ -1719,6 +1795,7 @@ tests = ["psycopg2-binary", "pytest (>=6.0.1)", "pytest-postgresql (>=2.4.0,<4.0 name = "pytest-mock" version = "3.10.0" description = "Thin-wrapper around the mock package for easier use with pytest" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1736,6 +1813,7 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] name = "pytest-xdist" version = "3.3.1" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1756,6 +1834,7 @@ testing = ["filelock"] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -1770,6 +1849,7 @@ six = ">=1.5" name = "pytz" version = "2022.7.1" description = "World timezone definitions, modern and historical" +category = "main" optional = false python-versions = "*" files = [ @@ -1781,6 +1861,7 @@ files = [ name = "pytz-deprecation-shim" version = "0.1.0.post0" description = "Shims to make deprecation of pytz easier" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -1795,6 +1876,7 @@ tzdata = {version = "*", markers = "python_version >= \"3.6\""} name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1844,6 +1926,7 @@ files = [ name = "regex" version = "2023.3.23" description = "Alternative regular expression module, to replace re." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1913,6 +1996,7 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1934,6 +2018,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "restrictedpython" version = "6.1" description = "RestrictedPython is a defined subset of the Python language which allows to provide a program input into a trusted environment." +category = "main" optional = false python-versions = ">=3.6, <3.12" files = [ @@ -1949,6 +2034,7 @@ test = ["pytest", "pytest-mock"] name = "ruamel-yaml" version = "0.17.21" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" +category = "dev" optional = false python-versions = ">=3" files = [ @@ -1967,6 +2053,7 @@ jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] name = "ruamel-yaml-clib" version = "0.2.7" description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1977,7 +2064,8 @@ files = [ {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win32.whl", hash = "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231"}, {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e"}, - {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_12_6_arm64.whl", hash = "sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:1a6391a7cabb7641c32517539ca42cf84b87b667bad38b78d4d42dd23e957c81"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:9c7617df90c1365638916b98cdd9be833d31d337dbcd722485597b43c4a215bf"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win32.whl", hash = "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122"}, @@ -2012,6 +2100,7 @@ files = [ name = "ruff" version = "0.0.270" description = "An extremely fast Python linter, written in Rust." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2038,6 +2127,7 @@ files = [ name = "safety" version = "2.3.5" description = "Checks installed dependencies for known vulnerabilities and licenses." +category = "dev" optional = false python-versions = "*" files = [ @@ -2061,6 +2151,7 @@ gitlab = ["python-gitlab (>=1.3.0)"] name = "sentry-sdk" version = "1.19.1" description = "Python client for Sentry (https://sentry.io)" +category = "main" optional = false python-versions = "*" files = [ @@ -2102,6 +2193,7 @@ tornado = ["tornado (>=5)"] name = "setuptools" version = "65.7.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2118,6 +2210,7 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "simplejson" version = "3.19.1" description = "Simple, fast, extensible JSON encoder/decoder for Python" +category = "main" optional = false python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -2212,6 +2305,7 @@ files = [ name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -2223,6 +2317,7 @@ files = [ name = "smmap" version = "5.0.0" description = "A pure Python implementation of a sliding window memory map manager" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2234,6 +2329,7 @@ files = [ name = "spiff-element-units" version = "0.3.0" description = "" +category = "main" optional = false python-versions = ">=3.9" files = [ @@ -2253,7 +2349,8 @@ files = [ [[package]] name = "SpiffWorkflow" version = "2.0.0rc0" -description = "A workflow framework and BPMN/DMN Processor" +description = "" +category = "main" optional = false python-versions = "*" files = [] @@ -2267,12 +2364,13 @@ lxml = "*" type = "git" url = "https://github.com/sartography/SpiffWorkflow" reference = "main" -resolved_reference = "ebcdde95c2f59f67981add1eacf9f5e04520d50f" +resolved_reference = "6b22a195b1841be3c8916e7826f8633173ff0a05" [[package]] name = "sqlalchemy" version = "2.0.9" description = "Database Abstraction Library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2320,7 +2418,7 @@ files = [ ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""} +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} typing-extensions = ">=4.2.0" [package.extras] @@ -2350,6 +2448,7 @@ sqlcipher = ["sqlcipher3-binary"] name = "sqlalchemy-stubs" version = "0.4" description = "SQLAlchemy stubs and mypy plugin" +category = "main" optional = false python-versions = "*" files = [] @@ -2369,6 +2468,7 @@ resolved_reference = "d1176931684ce5b327539cc9567d4a1cd8ef1efd" name = "stevedore" version = "5.0.0" description = "Manage dynamic plugins for Python applications" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2383,6 +2483,7 @@ pbr = ">=2.0.0,<2.1.0 || >2.1.0" name = "swagger-ui-bundle" version = "0.0.9" description = "swagger_ui_bundle - swagger-ui files in a pip package" +category = "main" optional = false python-versions = "*" files = [ @@ -2397,6 +2498,7 @@ Jinja2 = ">=2.0" name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -2408,6 +2510,7 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2419,6 +2522,7 @@ files = [ name = "typeguard" version = "3.0.2" description = "Run-time type checker for Python" +category = "dev" optional = false python-versions = ">=3.7.4" files = [ @@ -2437,6 +2541,7 @@ test = ["mypy (>=0.991)", "pytest (>=7)"] name = "types-click" version = "7.1.8" description = "Typing stubs for click" +category = "main" optional = false python-versions = "*" files = [ @@ -2448,6 +2553,7 @@ files = [ name = "types-dateparser" version = "1.1.4.9" description = "Typing stubs for dateparser" +category = "main" optional = false python-versions = "*" files = [ @@ -2459,6 +2565,7 @@ files = [ name = "types-flask" version = "1.1.6" description = "Typing stubs for Flask" +category = "main" optional = false python-versions = "*" files = [ @@ -2475,6 +2582,7 @@ types-Werkzeug = "*" name = "types-jinja2" version = "2.11.9" description = "Typing stubs for Jinja2" +category = "main" optional = false python-versions = "*" files = [ @@ -2489,6 +2597,7 @@ types-MarkupSafe = "*" name = "types-markupsafe" version = "1.1.10" description = "Typing stubs for MarkupSafe" +category = "main" optional = false python-versions = "*" files = [ @@ -2500,6 +2609,7 @@ files = [ name = "types-pytz" version = "2022.7.1.2" description = "Typing stubs for pytz" +category = "main" optional = false python-versions = "*" files = [ @@ -2511,6 +2621,7 @@ files = [ name = "types-pyyaml" version = "6.0.12.9" description = "Typing stubs for PyYAML" +category = "main" optional = false python-versions = "*" files = [ @@ -2522,6 +2633,7 @@ files = [ name = "types-requests" version = "2.28.11.17" description = "Typing stubs for requests" +category = "main" optional = false python-versions = "*" files = [ @@ -2536,6 +2648,7 @@ types-urllib3 = "<1.27" name = "types-urllib3" version = "1.26.25.10" description = "Typing stubs for urllib3" +category = "main" optional = false python-versions = "*" files = [ @@ -2547,6 +2660,7 @@ files = [ name = "types-werkzeug" version = "1.0.9" description = "Typing stubs for Werkzeug" +category = "main" optional = false python-versions = "*" files = [ @@ -2558,6 +2672,7 @@ files = [ name = "typing-extensions" version = "4.5.0" description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2569,6 +2684,7 @@ files = [ name = "tzdata" version = "2023.3" description = "Provider of IANA time zone data" +category = "main" optional = false python-versions = ">=2" files = [ @@ -2580,6 +2696,7 @@ files = [ name = "tzlocal" version = "4.3" description = "tzinfo object for the local timezone" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2598,6 +2715,7 @@ devenv = ["black", "check-manifest", "flake8", "pyroma", "pytest (>=4.3)", "pyte name = "urllib3" version = "1.26.15" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -2614,6 +2732,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "virtualenv" version = "20.21.0" description = "Virtual Python Environment builder" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2634,6 +2753,7 @@ test = ["covdefaults (>=2.2.2)", "coverage (>=7.1)", "coverage-enable-subprocess name = "werkzeug" version = "2.3.4" description = "The comprehensive WSGI web application library." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2651,6 +2771,7 @@ watchdog = ["watchdog (>=2.3)"] name = "wtforms" version = "3.0.1" description = "Form validation and rendering for Python web development." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2668,6 +2789,7 @@ email = ["email-validator"] name = "xdoctest" version = "1.1.1" description = "A rewrite of the builtin doctest module" +category = "dev" optional = false python-versions = ">=3.6" files = [ diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/data_migrations/version_1_3.py b/spiffworkflow-backend/src/spiffworkflow_backend/data_migrations/version_1_3.py new file mode 100644 index 00000000..444aeb83 --- /dev/null +++ b/spiffworkflow-backend/src/spiffworkflow_backend/data_migrations/version_1_3.py @@ -0,0 +1,195 @@ +import copy +import json +import uuid +from hashlib import sha256 + +from spiffworkflow_backend.models.db import db +from spiffworkflow_backend.models.task import Task +from spiffworkflow_backend.models.task import TaskModel # noqa: F401 +from spiffworkflow_backend.models.task_definition import TaskDefinitionModel +from sqlalchemy import or_ +from sqlalchemy.orm.attributes import flag_modified + + +class VersionOneThree: + """Migrates data in the database to be compatible with SpiffWorkflow at git revision ebcdde95. + + Converts migration file from SpiffWorkflow to work with backend's db: + https://github.com/sartography/SpiffWorkflow/blob/main/SpiffWorkflow/bpmn/serializer/migration/version_1_3.py + """ + + def run(self) -> None: + print("start VersionOneThree.run") + task_definitions = self.get_relevant_task_definitions() + print(f"found relevant task_definitions: {len(task_definitions)}") + for task_definition in task_definitions: + self.process_task_definition(task_definition) + relating_task_models = TaskModel.query.filter_by(task_definition_id=task_definition.id).all() + for task_model in relating_task_models: + self.process_task_model(task_model, task_definition) + + task_definitions_with_events = TaskDefinitionModel.query.filter( + or_( + TaskDefinitionModel.typename.like("%Event%"), # type: ignore + TaskDefinitionModel.typename.in_(["SendTask", "ReceiveTask"]), # type: ignore + ) + ).all() + for tdwe in task_definitions_with_events: + self.update_event_definitions(tdwe) + + db.session.commit() + print("end VersionOneThree.run") + + def get_relevant_task_definitions(self) -> list[TaskDefinitionModel]: + task_definitions: list[TaskDefinitionModel] = TaskDefinitionModel.query.filter_by( + typename="_BoundaryEventParent" + ).all() + return task_definitions + + def process_task_definition(self, task_definition: TaskDefinitionModel) -> None: + task_definition.typename = "BoundaryEventSplit" + task_definition.bpmn_identifier = task_definition.bpmn_identifier.replace( + "BoundaryEventParent", "BoundaryEventSplit" + ) + + properties_json = copy.copy(task_definition.properties_json) + properties_json.pop("main_child_task_spec") + properties_json["typename"] = task_definition.typename + properties_json["name"] = task_definition.bpmn_identifier + task_definition.properties_json = properties_json + flag_modified(task_definition, "properties_json") # type: ignore + db.session.add(task_definition) + + join_properties_json = { + "name": task_definition.bpmn_identifier.replace("BoundaryEventSplit", "BoundaryEventJoin"), + "manual": False, + "bpmn_id": None, + "lookahead": 2, + "inputs": properties_json["outputs"], + "outputs": [], + "split_task": task_definition.bpmn_identifier, + "threshold": None, + "cancel": True, + "typename": "BoundaryEventJoin", + } + + join_task_definition = TaskDefinitionModel( + bpmn_process_definition_id=task_definition.bpmn_process_definition_id, + bpmn_identifier=join_properties_json["name"], + typename=join_properties_json["typename"], + properties_json=join_properties_json, + ) + db.session.add(join_task_definition) + + for parent_bpmn_identifier in properties_json["inputs"]: + parent_task_definition = TaskDefinitionModel.query.filter_by( + bpmn_identifier=parent_bpmn_identifier, + bpmn_process_definition_id=task_definition.bpmn_process_definition_id, + ).first() + parent_task_definition.properties_json["outputs"] = [ + name.replace("BoundaryEventParent", "BoundaryEventSplit") + for name in parent_task_definition.properties_json["outputs"] + ] + flag_modified(parent_task_definition, "properties_json") # type: ignore + db.session.add(parent_task_definition) + + for child_bpmn_identifier in properties_json["outputs"]: + child_task_definition = TaskDefinitionModel.query.filter_by( + bpmn_identifier=child_bpmn_identifier, + bpmn_process_definition_id=task_definition.bpmn_process_definition_id, + ).first() + child_task_definition.properties_json["outputs"].append(join_task_definition.bpmn_identifier) + child_task_definition.properties_json["inputs"] = [ + name.replace("BoundaryEventParent", "BoundaryEventSplit") + for name in child_task_definition.properties_json["inputs"] + ] + flag_modified(child_task_definition, "properties_json") # type: ignore + db.session.add(child_task_definition) + + def process_task_model(self, task_model: TaskModel, task_definition: TaskDefinitionModel) -> None: + task_model.properties_json["task_spec"] = task_definition.bpmn_identifier + flag_modified(task_model, "properties_json") # type: ignore + db.session.add(task_model) + + child_task_models = [] + all_children_completed = True + + # Ruff keeps complaining unless it's done like this + blank_json = json.dumps({}) + blank_json_data_hash = sha256(blank_json.encode("utf8")).hexdigest() + + for child_task_guid in task_model.properties_json["children"]: + child_task_model = TaskModel.query.filter_by(guid=child_task_guid).first() + if child_task_model is None: + continue + if child_task_model.state not in ["COMPLETED", "CANCELLED"]: + all_children_completed = False + child_task_models.append(child_task_model) + + for child_task_model in child_task_models: + if child_task_model.state == "CANCELLED": + # Cancelled tasks don't have children + continue + + new_task_state = None + start_in_seconds = child_task_model.start_in_seconds + end_in_seconds = None + + if child_task_model.state in ["MAYBE", "LIKELY", "FUTURE"]: + new_task_state = child_task_model.state + elif child_task_model.state in ["WAITING", "READY", "STARTED"]: + new_task_state = "FUTURE" + elif child_task_model.state == "COMPLETED": + if all_children_completed: + new_task_state = "COMPLETED" + end_in_seconds = child_task_model.end_in_seconds + else: + new_task_state = "WAITING" + elif child_task_model.state == "ERROR": + new_task_state = "WAITING" + else: + raise Exception(f"Unknown state: {child_task_model.state} for {child_task_model.guild}") + + new_task_properties_json = { + "id": str(uuid.uuid4()), + "parent": child_task_model.guid, + "children": [], + "state": Task.task_state_name_to_int(new_task_state), + "task_spec": task_definition.bpmn_identifier.replace("BoundaryEventSplit", "BoundaryEventJoin"), + "last_state_change": None, + "triggered": False, + "internal_data": {}, + } + + new_task_model = TaskModel( + guid=new_task_properties_json["id"], + bpmn_process_id=task_model.bpmn_process_id, + process_instance_id=task_model.process_instance_id, + task_definition_id=task_model.task_definition_id, + state=new_task_state, + properties_json=new_task_properties_json, + start_in_seconds=start_in_seconds, + end_in_seconds=end_in_seconds, + json_data_hash=blank_json_data_hash, + python_env_data_hash=blank_json_data_hash, + ) + db.session.add(new_task_model) + + child_task_model.properties_json["children"].append(new_task_model.guid) + flag_modified(child_task_model, "properties_json") # type: ignore + db.session.add(child_task_model) + + def update_event_definitions(self, task_definition: TaskDefinitionModel) -> None: + if "event_definition" in task_definition.properties_json: + properties_json = copy.copy(task_definition.properties_json) + properties_json["event_definition"].pop("internal", None) + properties_json["event_definition"].pop("external", None) + if "escalation_code" in properties_json["event_definition"]: + properties_json["event_definition"]["code"] = properties_json["event_definition"].pop( + "escalation_code" + ) + if "error_code" in properties_json["event_definition"]: + properties_json["event_definition"]["code"] = properties_json["event_definition"].pop("error_code") + task_definition.properties_json = properties_json + flag_modified(task_definition, "properties_json") # type: ignore + db.session.add(task_definition) diff --git a/spiffworkflow-frontend/package-lock.json b/spiffworkflow-frontend/package-lock.json index c5c82559..9436172d 100644 --- a/spiffworkflow-frontend/package-lock.json +++ b/spiffworkflow-frontend/package-lock.json @@ -8351,7 +8351,7 @@ }, "node_modules/bpmn-js-spiffworkflow": { "version": "0.0.8", - "resolved": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#1bc43155d5f10b69ebfcfeeb136e7badec21a4bf", + "resolved": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#877424a55ab5e4f25a06ce480f0f5c0cf72ea150", "license": "MIT", "dependencies": { "inherits": "^2.0.4", @@ -38314,7 +38314,7 @@ } }, "bpmn-js-spiffworkflow": { - "version": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#1bc43155d5f10b69ebfcfeeb136e7badec21a4bf", + "version": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#877424a55ab5e4f25a06ce480f0f5c0cf72ea150", "from": "bpmn-js-spiffworkflow@github:sartography/bpmn-js-spiffworkflow#main", "requires": { "inherits": "^2.0.4",