diff --git a/SpiffWorkflow/.github/workflows/tests.yaml b/SpiffWorkflow/.github/workflows/tests.yaml new file mode 100644 index 000000000..cc1ccd4e1 --- /dev/null +++ b/SpiffWorkflow/.github/workflows/tests.yaml @@ -0,0 +1,32 @@ +name: Unit Tests in Python 3.8, 3.9, 3.10, 3.11 + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install ruff pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi +# - name: Lint with ruff +# run: | + # stop the build if there are Python syntax errors or undefined names +# ruff --format=github --select=E9,F63,F7,F82 --target-version=py37 . + # default set of ruff rules with GitHub Annotations + # ruff --format=github --target-version=py37 . + - name: Test with pytest + run: | + python -m unittest discover -v -s ./tests/SpiffWorkflow/ -p *Test.py diff --git a/SpiffWorkflow/.sonarcloud.properties b/SpiffWorkflow/.sonarcloud.properties deleted file mode 100644 index 5b04a73e9..000000000 --- a/SpiffWorkflow/.sonarcloud.properties +++ /dev/null @@ -1,7 +0,0 @@ -sonar.organization=sartography -sonar.projectKey=sartography_SpiffWorkflow -sonar.host.url=https://sonarcloud.io -sonar.exclusions=*.bpmn,*.dmn,doc/** -sonar.sources=SpiffWorkflow -sonar.test.inclusions=tests -sonar.python.coverage.reportPaths=tests/SpiffWorkflow/coverage.xml diff --git a/SpiffWorkflow/CONTRIB b/SpiffWorkflow/CONTRIB index ae92ee017..db52f1497 100644 --- a/SpiffWorkflow/CONTRIB +++ b/SpiffWorkflow/CONTRIB @@ -57,4 +57,4 @@ New versions of SpiffWorkflow are automatically published to PyPi whenever a maintainer of our GitHub repository creates a new release on GitHub. This is managed through GitHub's actions. The configuration of which can be found in .github/workflows/.... -Just create a release in GitHub that mathches the release number in doc/conf.py +Just create a release in GitHub that matches the release number in doc/conf.py diff --git a/SpiffWorkflow/README.md b/SpiffWorkflow/README.md index e00ff6be2..071979a14 100644 --- a/SpiffWorkflow/README.md +++ b/SpiffWorkflow/README.md @@ -19,9 +19,7 @@ strategy for building Low-Code applications. ## Build status [![Build Status](https://travis-ci.com/sartography/SpiffWorkflow.svg?branch=master)](https://travis-ci.org/sartography/SpiffWorkflow) -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=sartography_SpiffWorkflow&metric=alert_status)](https://sonarcloud.io/dashboard?id=sartography_SpiffWorkflow) -[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=sartography_SpiffWorkflow&metric=coverage)](https://sonarcloud.io/dashboard?id=sartography_SpiffWorkflow) -[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=sartography_SpiffWorkflow&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=sartography_SpiffWorkflow) +[![SpiffWorkflow](https://github.com/sartography/SpiffWorkflow/actions/workflows/tests.yaml/badge.svg)](https://github.com/sartography/SpiffWorkflow/actions/workflows/tests.yaml) [![Documentation Status](https://readthedocs.org/projects/spiffworkflow/badge/?version=latest)](http://spiffworkflow.readthedocs.io/en/latest/?badge=latest) [![Issues](https://img.shields.io/github/issues/sartography/spiffworkflow)](https://github.com/sartography/SpiffWorkflow/issues) [![Pull Requests](https://img.shields.io/github/issues-pr/sartography/spiffworkflow)](https://github.com/sartography/SpiffWorkflow/pulls) diff --git a/SpiffWorkflow/RELEASE_NOTES.md b/SpiffWorkflow/RELEASE_NOTES.md new file mode 100644 index 000000000..1bfe60ede --- /dev/null +++ b/SpiffWorkflow/RELEASE_NOTES.md @@ -0,0 +1,138 @@ +## What's Changed + +We've done a lot of work over the last 8 months to the SpiffWorkflow library as we've developed [SpiffArena](https://www.spiffworkflow.org/), a general purpose workflow management system built on top of this library. +This has resulted in just a handful of new features. +Our main focus was on making SpiffWorkflow more predictable, easier to use, and internally consistent. + +## Breaking Changes from 1.x: +* We heavily refactored the way we handle multi-instance tasks internally. This will break any serialized workflows that contain multi-instance tasks. +* Internal structure of our code, the names classes, and common methods have changed. Please see our [ReadTheDocs] (https://readthedocs.org/projects/spiffworkflow/) documenation for version 2.0.0. + +## Features and Improvements + +### Task States, Transitions, Hooks, and Execution +Previous to 2.0, SpiffWorklow was a little weird about its states, performing the actual execution in the on_complete() hook. +This was VERY confusing. +Tasks now have a _run() command separate from state change hooks. +The return value of the _run() command can be true (worked), false (failure), or None (not yet done). +This opens the door for better overall state management at the moment it is most critical (when the task is actually executing). +We also added new task state called "STARTED" that describes when a task was started, but hasn't finished yet, an oddly missing state in previous versions. + +* Improvement/execution and serialization cleanup by @essweine in https://github.com/sartography/SpiffWorkflow/pull/289 +* Bugfix/execute tasks on ready by @essweine in https://github.com/sartography/SpiffWorkflow/pull/303 +* Feature/standardize task execution by @essweine in https://github.com/sartography/SpiffWorkflow/pull/307 +* do not execute boundary events in catch by @essweine in https://github.com/sartography/SpiffWorkflow/pull/312 +* Feature/new task states by @essweine in https://github.com/sartography/SpiffWorkflow/pull/315 + +### Improved Events +We refactored the way we handle events, making them more powerful and adaptable. +Timer events are now parsed according to the [ISO 8601 standard](https://en.wikipedia.org/wiki/ISO_8601). +* Feature/multiple event definition by @essweine in https://github.com/sartography/SpiffWorkflow/pull/268 +* hacks to handle timer events like regular events by @essweine in https://github.com/sartography/SpiffWorkflow/pull/273 +* Feature/improved timer events by @essweine in https://github.com/sartography/SpiffWorkflow/pull/284 +* reset boundary events in loops by @essweine in https://github.com/sartography/SpiffWorkflow/pull/294 +* Bugfix/execute event gateways on ready by @essweine in https://github.com/sartography/SpiffWorkflow/pull/308 + +### Improved Multi-Instance Tasks +We refactored how Multi-instance tasks are handled internally, vastly simplifying their representation during execution and serialization. +No more 'phantom gateways.' +* Feature/multiinstance refactor by @essweine in https://github.com/sartography/SpiffWorkflow/pull/292 + +### Improved SubProcesses +SpiffWorkflow did not previously distinguish between a Call Activity and a SubProcess, but they handle Data Objects very differently. +A SubProcess is now able to access its parent data objects, a Call Activity can not. +We also wanted the ability to execute Call Activities independently of the parent process. + +* Bugfix/subprocess access to data objects by @essweine in https://github.com/sartography/SpiffWorkflow/pull/296 +* start workflow while subprocess is waiting by @essweine in https://github.com/sartography/SpiffWorkflow/pull/302 +* use same data objects & references in subprocesses after deserialization by @essweine in https://github.com/sartography/SpiffWorkflow/pull/314 + +### Improved Data Objects / Data Stores +This work will continue in subsequent releases, but we have added support for Data Stores, and it is possible to provide your own implementations. +* Data stores by @jbirddog in https://github.com/sartography/SpiffWorkflow/pull/298 +* make data objects available to gateways by @essweine in https://github.com/sartography/SpiffWorkflow/pull/325 + +### Improved Inclusive Gateways +We added support for Inclusive Gateways. +* Feature/inclusive gateway support by @essweine in https://github.com/sartography/SpiffWorkflow/pull/286 + +### Pre and Post Script Fixes +We previously supported adding a pre-script or post-script to any task but there were a few lingering bugs that needed fixing. +* parse spiff script extensions in service tasks by @essweine in https://github.com/sartography/SpiffWorkflow/pull/257 +* pass script to workflow task exec exception by @essweine in https://github.com/sartography/SpiffWorkflow/pull/258 +* update execution order for postscripts by @essweine in https://github.com/sartography/SpiffWorkflow/pull/259 + +### DMN Improvements +We now support a new hit policy of "COLLECT" which allows you to match on an array of items. DMN support is still limited, but +we are making headway. We would love to know if people are using these features. +* Support for the "COLLECT" hit policy. by @danfunk in https://github.com/sartography/SpiffWorkflow/pull/267 +* Bugfix/handle dash in DMN by @essweine in https://github.com/sartography/SpiffWorkflow/pull/323 + +### BPMN Validation +We improved validation of BPMN and DMN Files to catch errors earlier. +* Feature/xml validation by @essweine and @danfunk in https://github.com/sartography/SpiffWorkflow/pull/256 + +### New Serializer +There are some breaking changes in the new serializer, but it is much faster and more stable. We do attempt to upgrade +your serialized workflows to the new format, but you will definitely encounter issues if you were using multi-instance tasks. +* update serializer version by @essweine in https://github.com/sartography/SpiffWorkflow/pull/277 +* Feature/remove old serializer by @essweine in https://github.com/sartography/SpiffWorkflow/pull/278 + +### Lightning Fast, Stable Tests +* Fix ResourceWarning: unclosed file BpmnParser.py:60 by @jbirddog in https://github.com/sartography/SpiffWorkflow/pull/270 +* Option to run tests in parallel by @jbirddog in https://github.com/sartography/SpiffWorkflow/pull/271 + +### Better Errors +* Feature/better errors by @danfunk in https://github.com/sartography/SpiffWorkflow/pull/283 +* Workflow Data Exceptions were broken in the previous error refactor. … by @danfunk in https://github.com/sartography/SpiffWorkflow/pull/287 +* added an exception for task not found w/ @burnettk by @jasquat in https://github.com/sartography/SpiffWorkflow/pull/310 +* give us a better error if for some reason a task does not exist by @burnettk in https://github.com/sartography/SpiffWorkflow/pull/311 + +### Flexible Data Management +* Allow for other PythonScriptEngine environments besides task data by @jbirddog in https://github.com/sartography/SpiffWorkflow/pull/288 + +### Various Enhancements +Make it easier to reference SpiffWorkflow library classes from your own code. +* Feature/add init to schema by @jasquat in https://github.com/sartography/SpiffWorkflow/pull/260 +* cleaning up code smell by @danfunk in https://github.com/sartography/SpiffWorkflow/pull/261 +* Feature/cleanup task completion by @essweine in https://github.com/sartography/SpiffWorkflow/pull/263 +* disambiguate DMN expressions by @essweine in https://github.com/sartography/SpiffWorkflow/pull/264 +* Add in memory BPMN/DMN parser functions by @jbirddog in https://github.com/sartography/SpiffWorkflow/pull/320 + +### Better Introspection +Added the ability to ask SpiffWorkflow some useful questions about a specification such as, "What call activities does this depend on?", +"What messages does this process send and receive", and "What lanes exist on this workflow specification?" +* Parser Information about messages, correlation keys, and the presence of lanes by @danfunk in https://github.com/sartography/SpiffWorkflow/pull/262 +* Called elements by @jbirddog in https://github.com/sartography/SpiffWorkflow/pull/316 + +### Code Cleanup +* Improvement/task spec attributes by @essweine in https://github.com/sartography/SpiffWorkflow/pull/328 +* update license by @essweine in https://github.com/sartography/SpiffWorkflow/pull/324 +* Feature/remove unused BPMN attributes and methods by @essweine in https://github.com/sartography/SpiffWorkflow/pull/280 +* Improvement/remove camunda from base and misc cleanup by @essweine in https://github.com/sartography/SpiffWorkflow/pull/295 +* remove minidom by @essweine in https://github.com/sartography/SpiffWorkflow/pull/300 +* Feature/remove loop reset by @essweine in https://github.com/sartography/SpiffWorkflow/pull/305 +* Feature/create core test package by @essweine in https://github.com/sartography/SpiffWorkflow/pull/306 +* remove celery task and dependency by @essweine in https://github.com/sartography/SpiffWorkflow/pull/322 +* remove one deprecated and unused feature by @essweine in https://github.com/sartography/SpiffWorkflow/pull/329 +* change the order of tasks when calling get_tasks() by @danfunk in https://github.com/sartography/SpiffWorkflow/pull/319 + +### Improved Documentation +* Fixes grammar, typos, and spellings by @rachfop in https://github.com/sartography/SpiffWorkflow/pull/291 +* Updates for 2.0 release by @essweine in https://github.com/sartography/SpiffWorkflow/pull/330 +* Bugfix/non BPMN tutorial by @essweine in https://github.com/sartography/SpiffWorkflow/pull/317 + +### Bug Fixes +* correct xpath for extensions by @essweine in https://github.com/sartography/SpiffWorkflow/pull/265 +* prevent output associations from being removed twice by @essweine in https://github.com/sartography/SpiffWorkflow/pull/275 +* fix for workflowspec dump by @subhakarks in https://github.com/sartography/SpiffWorkflow/pull/282 +* add checks for len == 0 when copying based on io spec by @essweine in https://github.com/sartography/SpiffWorkflow/pull/297 +* Improvement/allow duplicate subprocess names by @essweine in https://github.com/sartography/SpiffWorkflow/pull/321 +* Resets to tasks with Boundary Events by @danfunk in https://github.com/sartography/SpiffWorkflow/pull/326 +* Sub-workflow tasks should be marked as "Future" when resetting to a task before the sub-process. by @danfunk in https://github.com/sartography/SpiffWorkflow/pull/327 + +## New Contributors +* @subhakarks made their first contribution in https://github.com/sartography/SpiffWorkflow/pull/282 +* @rachfop made their first contribution in https://github.com/sartography/SpiffWorkflow/pull/291 + +**Full Changelog**: https://github.com/sartography/SpiffWorkflow/compare/v1.2.1...v2.0.0 diff --git a/SpiffWorkflow/SpiffWorkflow/__init__.py b/SpiffWorkflow/SpiffWorkflow/__init__.py index e69de29bb..d50ddaba8 100644 --- a/SpiffWorkflow/SpiffWorkflow/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/__init__.py @@ -0,0 +1,16 @@ +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/FeelLikeScriptEngine.py b/SpiffWorkflow/SpiffWorkflow/bpmn/FeelLikeScriptEngine.py index a4db53082..9789d58c1 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/FeelLikeScriptEngine.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/FeelLikeScriptEngine.py @@ -1,20 +1,13 @@ -# -*- coding: utf-8 -*- - -import re -import datetime -import operator -from datetime import timedelta -from decimal import Decimal -from .PythonScriptEngine import PythonScriptEngine - -# Copyright (C) 2020 Kelly McDonald +# Copyright (C) 2020 Kelly McDonald, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -24,6 +17,13 @@ from .PythonScriptEngine import PythonScriptEngine # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA +import re +import datetime +import operator +from datetime import timedelta +from decimal import Decimal +from .PythonScriptEngine import PythonScriptEngine + def feelConvertTime(datestr,parsestr): return datetime.datetime.strptime(datestr,parsestr) @@ -79,8 +79,8 @@ class FeelNot(): def feelConcatenate(*lst): ilist = [] - for l in lst: - ilist = ilist + l + for list_item in lst: + ilist = ilist + list_item return ilist def feelAppend(lst,item): @@ -144,7 +144,7 @@ def feelFilter(var,a,b,op,column=None): newvar.append({'key':key,'value':var[key]}) var = newvar - if column!=None: + if column is not None: return [x.get(column) for x in var if opmap[op](x.get(a), b)] else: return [x for x in var if opmap[op](x.get(a), b)] @@ -306,7 +306,7 @@ class FeelLikeScriptEngine(PythonScriptEngine): if external_methods is None: external_methods = {} external_methods.update(externalFuncs) - super(PythonScriptEngine).execute(task, script, external_methods) + super().execute(task, script, external_methods) diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/PythonScriptEngine.py b/SpiffWorkflow/SpiffWorkflow/bpmn/PythonScriptEngine.py index 6e9550d4f..0782ee92e 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/PythonScriptEngine.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/PythonScriptEngine.py @@ -1,21 +1,13 @@ -# -*- coding: utf-8 -*- -import ast -import sys -import traceback -import warnings - -from .PythonScriptEngineEnvironment import TaskDataEnvironment -from ..exceptions import SpiffWorkflowException, WorkflowTaskException - - -# Copyright (C) 2020 Kelly McDonald +# Copyright (C) 2020 Kelly McDonald, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -25,6 +17,15 @@ from ..exceptions import SpiffWorkflowException, WorkflowTaskException # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA +import ast +import sys +import traceback +import warnings + +from SpiffWorkflow.exceptions import SpiffWorkflowException +from SpiffWorkflow.bpmn.exceptions import WorkflowTaskException +from .PythonScriptEngineEnvironment import TaskDataEnvironment + class PythonScriptEngine(object): """ @@ -40,8 +41,8 @@ class PythonScriptEngine(object): def __init__(self, default_globals=None, scripting_additions=None, environment=None): if default_globals is not None or scripting_additions is not None: - warnings.warn(f'default_globals and scripting_additions are deprecated. ' - f'Please provide an environment such as TaskDataEnvrionment', + warnings.warn('default_globals and scripting_additions are deprecated. ' + 'Please provide an environment such as TaskDataEnvrionment', DeprecationWarning, stacklevel=2) if environment is None: diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/PythonScriptEngineEnvironment.py b/SpiffWorkflow/SpiffWorkflow/bpmn/PythonScriptEngineEnvironment.py index 1b07b4782..d90f21727 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/PythonScriptEngineEnvironment.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/PythonScriptEngineEnvironment.py @@ -1,3 +1,22 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + import copy import warnings @@ -96,7 +115,7 @@ class Box(dict): def __getattr__(self, attr): try: output = self[attr] - except: + except Exception: raise AttributeError( "Dictionary has no attribute '%s' " % str(attr)) return output diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/__init__.py b/SpiffWorkflow/SpiffWorkflow/bpmn/__init__.py index 9a31a4077..96b2e5e5f 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/__init__.py @@ -1,12 +1,13 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2012 Matthew Hampton +# Copyright (C) 2012 Matthew Hampton, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -14,4 +15,4 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 USA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/exceptions.py b/SpiffWorkflow/SpiffWorkflow/bpmn/exceptions.py index df2ddd2c3..28c43dec1 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/exceptions.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/exceptions.py @@ -1,4 +1,90 @@ -from SpiffWorkflow.exceptions import WorkflowTaskException +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA +import re + +from SpiffWorkflow.util import levenshtein +from SpiffWorkflow.exceptions import WorkflowException + + +class WorkflowTaskException(WorkflowException): + """WorkflowException that provides task_trace information.""" + + def __init__(self, error_msg, task=None, exception=None, line_number=None, offset=None, error_line=None): + """ + Exception initialization. + + :param task: the task that threw the exception + :type task: Task + :param error_msg: a human readable error message + :type error_msg: str + :param exception: an exception to wrap, if any + :type exception: Exception + """ + self.task = task + self.line_number = line_number + self.offset = offset + self.error_line = error_line + if exception: + self.error_type = exception.__class__.__name__ + else: + self.error_type = "unknown" + super().__init__(error_msg, task_spec=task.task_spec) + + if isinstance(exception, SyntaxError) and not line_number: + # Line number and offset can be recovered directly from syntax errors, + # otherwise they must be passed in. + self.line_number = exception.lineno + self.offset = exception.offset + elif isinstance(exception, NameError): + self.add_note(self.did_you_mean_from_name_error(exception, list(task.data.keys()))) + + # If encountered in a sub-workflow, this traces back up the stack, + # so we can tell how we got to this particular task, no matter how + # deeply nested in sub-workflows it is. Takes the form of: + # task-description (file-name) + self.task_trace = self.get_task_trace(task) + + @staticmethod + def get_task_trace(task): + task_trace = [f"{task.task_spec.bpmn_name} ({task.workflow.spec.file})"] + workflow = task.workflow + while workflow != workflow.outer_workflow: + caller = workflow.name + workflow = workflow.outer_workflow + task_trace.append(f"{workflow.spec.task_specs[caller].bpmn_name} ({workflow.spec.file})") + return task_trace + + @staticmethod + def did_you_mean_from_name_error(name_exception, options): + """Returns a string along the lines of 'did you mean 'dog'? Given + a name_error, and a set of possible things that could have been called, + or an empty string if no valid suggestions come up. """ + def_match = re.match("name '(.+)' is not defined", str(name_exception)) + if def_match: + bad_variable = re.match("name '(.+)' is not defined", str(name_exception)).group(1) + most_similar = levenshtein.most_similar(bad_variable, options, 3) + error_msg = "" + if len(most_similar) == 1: + error_msg += f' Did you mean \'{most_similar[0]}\'?' + if len(most_similar) > 1: + error_msg += f' Did you mean one of \'{most_similar}\'?' + return error_msg class WorkflowDataException(WorkflowTaskException): diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/BpmnParser.py b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/BpmnParser.py index 60e93f947..0a6c5107b 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/BpmnParser.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/BpmnParser.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2012 Matthew Hampton # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -23,26 +23,36 @@ import os from lxml import etree from lxml.etree import LxmlError -from SpiffWorkflow.bpmn.specs.events.event_definitions import NoneEventDefinition +from SpiffWorkflow.bpmn.specs.bpmn_process_spec import BpmnProcessSpec +from SpiffWorkflow.bpmn.specs.defaults import ( + UserTask, + ManualTask, + NoneTask, + ScriptTask, + ServiceTask, + CallActivity, + SubWorkflowTask, + TransactionSubprocess, + InclusiveGateway, + ExclusiveGateway, + ParallelGateway, + StartEvent, + EndEvent, + IntermediateCatchEvent, + IntermediateThrowEvent, + SendTask, + ReceiveTask, + BoundaryEvent, + EventBasedGateway +) +from SpiffWorkflow.bpmn.specs.event_definitions import NoneEventDefinition, TimerEventDefinition +from SpiffWorkflow.bpmn.specs.mixins.subworkflow_task import SubWorkflowTask as SubWorkflowTaskMixin +from SpiffWorkflow.bpmn.specs.mixins.events.start_event import StartEvent as StartEventMixin from .ValidationException import ValidationException -from ..specs.BpmnProcessSpec import BpmnProcessSpec -from ..specs.data_spec import BpmnDataStoreSpecification -from ..specs.events.EndEvent import EndEvent -from ..specs.events.StartEvent import StartEvent -from ..specs.events.IntermediateEvent import BoundaryEvent, IntermediateCatchEvent, IntermediateThrowEvent, EventBasedGateway -from ..specs.events.IntermediateEvent import SendTask, ReceiveTask -from ..specs.SubWorkflowTask import CallActivity, SubWorkflowTask, TransactionSubprocess -from ..specs.ExclusiveGateway import ExclusiveGateway -from ..specs.InclusiveGateway import InclusiveGateway -from ..specs.ManualTask import ManualTask -from ..specs.NoneTask import NoneTask -from ..specs.ParallelGateway import ParallelGateway -from ..specs.ScriptTask import ScriptTask -from ..specs.ServiceTask import ServiceTask -from ..specs.UserTask import UserTask from .ProcessParser import ProcessParser from .node_parser import DEFAULT_NSMAP +from .spec_description import SPEC_DESCRIPTIONS from .util import full_tag, xpath_eval, first from .TaskParser import TaskParser from .task_parsers import ( @@ -86,7 +96,7 @@ class BpmnValidator: except ValidationException as ve: ve.file_name = filename ve.line_number = self.validator.error_log.last_error.line - except LxmlError as le: + except LxmlError: last_error = self.validator.error_log.last_error raise ValidationException(last_error.message, file_name=filename, line_number=last_error.line) @@ -132,14 +142,14 @@ class BpmnParser(object): DATA_STORE_CLASSES = {} - def __init__(self, namespaces=None, validator=None): + def __init__(self, namespaces=None, validator=None, spec_descriptions=SPEC_DESCRIPTIONS): """ Constructor. """ self.namespaces = namespaces or DEFAULT_NSMAP self.validator = validator + self.spec_descriptions = spec_descriptions self.process_parsers = {} - self.process_parsers_by_name = {} self.collaborations = {} self.process_dependencies = set() self.messages = {} @@ -153,15 +163,13 @@ class BpmnParser(object): return self.PARSER_CLASSES[tag] return None, None - def get_process_parser(self, process_id_or_name): + def get_process_parser(self, process_id): """ Returns the ProcessParser for the given process ID or name. It matches by name first. """ - if process_id_or_name in self.process_parsers_by_name: - return self.process_parsers_by_name[process_id_or_name] - elif process_id_or_name in self.process_parsers: - return self.process_parsers[process_id_or_name] + if process_id in self.process_parsers: + return self.process_parsers[process_id] def get_process_ids(self): """Returns a list of process IDs""" @@ -186,7 +194,19 @@ class BpmnParser(object): """ for filename in filenames: with open(filename, 'r') as f: - self.add_bpmn_xml(etree.parse(f), filename=filename) + self.add_bpmn_io(f, filename) + + def add_bpmn_io(self, file_like_object, filename=None): + """ + Add the given BPMN file like object to the parser's set. + """ + self.add_bpmn_xml(etree.parse(file_like_object), filename) + + def add_bpmn_str(self, bpmn_str, filename=None): + """ + Add the given BPMN string to the parser's set. + """ + self.add_bpmn_xml(etree.fromstring(bpmn_str), filename) def add_bpmn_xml(self, bpmn, filename=None): """ @@ -286,37 +306,37 @@ class BpmnParser(object): def create_parser(self, node, filename=None, lane=None): parser = self.PROCESS_PARSER_CLASS(self, node, self.namespaces, self.data_stores, filename=filename, lane=lane) - if parser.get_id() in self.process_parsers: - raise ValidationException(f'Duplicate process ID: {parser.get_id()}', node=node, file_name=filename) - if parser.get_name() in self.process_parsers_by_name: - raise ValidationException(f'Duplicate process name: {parser.get_name()}', node=node, file_name=filename) - self.process_parsers[parser.get_id()] = parser - self.process_parsers_by_name[parser.get_name()] = parser + if parser.bpmn_id in self.process_parsers: + raise ValidationException(f'Duplicate process ID: {parser.bpmn_id}', node=node, file_name=filename) + self.process_parsers[parser.bpmn_id] = parser def get_process_dependencies(self): return self.process_dependencies - def get_spec(self, process_id_or_name): + def get_spec(self, process_id, required=True): """ Parses the required subset of the BPMN files, in order to provide an instance of BpmnProcessSpec (i.e. WorkflowSpec) for the given process ID or name. The Name is matched first. """ - parser = self.get_process_parser(process_id_or_name) - if parser is None: + parser = self.get_process_parser(process_id) + if required and parser is None: raise ValidationException( - f"The process '{process_id_or_name}' was not found. " + f"The process '{process_id}' was not found. " f"Did you mean one of the following: " f"{', '.join(self.get_process_ids())}?") - return parser.get_spec() + elif parser is not None: + return parser.get_spec() - def get_subprocess_specs(self, name, specs=None): + def get_subprocess_specs(self, name, specs=None, require_call_activity_specs=True): used = specs or {} wf_spec = self.get_spec(name) for task_spec in wf_spec.task_specs.values(): - if isinstance(task_spec, SubWorkflowTask) and task_spec.spec not in used: - used[task_spec.spec] = self.get_spec(task_spec.spec) - self.get_subprocess_specs(task_spec.spec, used) + if isinstance(task_spec, SubWorkflowTaskMixin) and task_spec.spec not in used: + subprocess_spec = self.get_spec(task_spec.spec, required=require_call_activity_specs) + used[task_spec.spec] = subprocess_spec + if subprocess_spec is not None: + self.get_subprocess_specs(task_spec.spec, used) return used def find_all_specs(self): @@ -332,16 +352,24 @@ class BpmnParser(object): self.find_all_specs() spec = BpmnProcessSpec(name) subprocesses = {} - start = StartEvent(spec, 'Start Collaboration', NoneEventDefinition()) + participant_type = self._get_parser_class(full_tag('callActivity'))[1] + start_type = self._get_parser_class(full_tag('startEvent'))[1] + end_type = self._get_parser_class(full_tag('endEvent'))[1] + start = start_type(spec, 'Start Collaboration', NoneEventDefinition()) spec.start.connect(start) - end = EndEvent(spec, 'End Collaboration', NoneEventDefinition()) + end = end_type(spec, 'End Collaboration', NoneEventDefinition()) end.connect(spec.end) for process in self.collaborations[name]: process_parser = self.get_process_parser(process) if process_parser and process_parser.process_executable: - participant = CallActivity(spec, process, process) - start.connect(participant) - participant.connect(end) - subprocesses[process] = self.get_spec(process) + sp_spec = self.get_spec(process) + subprocesses[process] = sp_spec subprocesses.update(self.get_subprocess_specs(process)) + if len([s for s in sp_spec.task_specs.values() if + isinstance(s, StartEventMixin) and + isinstance(s.event_definition, (NoneEventDefinition, TimerEventDefinition)) + ]): + participant = participant_type(spec, process, process) + start.connect(participant) + participant.connect(end) return spec, subprocesses diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/ProcessParser.py b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/ProcessParser.py index 18ddf3b2a..59f6c5b87 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/ProcessParser.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/ProcessParser.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2012 Matthew Hampton # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -18,7 +18,7 @@ # 02110-1301 USA from .ValidationException import ValidationException -from ..specs.BpmnProcessSpec import BpmnProcessSpec +from ..specs.bpmn_process_spec import BpmnProcessSpec from ..specs.data_spec import DataObject from .node_parser import NodeParser from .util import first @@ -54,7 +54,7 @@ class ProcessParser(NodeParser): """ Returns the process name (or ID, if no name is included in the file) """ - return self.node.get('name', default=self.get_id()) + return self.node.get('name', default=self.bpmn_id) def has_lanes(self) -> bool: """Returns true if this process has one or more named lanes """ @@ -117,14 +117,14 @@ class ProcessParser(NodeParser): start_node_list = self.xpath('./bpmn:startEvent') if not start_node_list and self.process_executable: raise ValidationException("No start event found", node=self.node, file_name=self.filename) - self.spec = BpmnProcessSpec(name=self.get_id(), description=self.get_name(), filename=self.filename) + self.spec = BpmnProcessSpec(name=self.bpmn_id, description=self.get_name(), filename=self.filename) self.spec.data_objects.update(self.inherited_data_objects) # Get the data objects for obj in self.xpath('./bpmn:dataObject'): data_object = self.parse_data_object(obj) - self.spec.data_objects[data_object.name] = data_object + self.spec.data_objects[data_object.bpmn_id] = data_object # Check for an IO Specification. io_spec = first(self.xpath('./bpmn:ioSpecification')) diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/TaskParser.py b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/TaskParser.py index 19a426ccd..90910604e 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/TaskParser.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/TaskParser.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2012 Matthew Hampton +# Copyright (C) 2012 Matthew Hampton, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -17,17 +17,21 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -from .ValidationException import ValidationException -from ..specs.events.IntermediateEvent import _BoundaryEventParent -from ..specs.events.event_definitions import CancelEventDefinition -from ..specs.MultiInstanceTask import StandardLoopTask, SequentialMultiInstanceTask, ParallelMultiInstanceTask -from ..specs.SubWorkflowTask import TransactionSubprocess -from ..specs.ExclusiveGateway import ExclusiveGateway -from ..specs.InclusiveGateway import InclusiveGateway -from ..specs.data_spec import TaskDataReference +from SpiffWorkflow.bpmn.specs.mixins.subworkflow_task import TransactionSubprocess +from SpiffWorkflow.bpmn.specs.mixins.exclusive_gateway import ExclusiveGateway +from SpiffWorkflow.bpmn.specs.mixins.inclusive_gateway import InclusiveGateway +from SpiffWorkflow.bpmn.specs.defaults import ( + StandardLoopTask, + SequentialMultiInstanceTask, + ParallelMultiInstanceTask +) +from SpiffWorkflow.bpmn.specs.control import _BoundaryEventParent +from SpiffWorkflow.bpmn.specs.event_definitions import CancelEventDefinition +from SpiffWorkflow.bpmn.specs.data_spec import TaskDataReference from .util import one from .node_parser import NodeParser +from .ValidationException import ValidationException class TaskParser(NodeParser): @@ -94,15 +98,17 @@ class TaskParser(NodeParser): self._copy_task_attrs(original) def _add_multiinstance_task(self, loop_characteristics): - + sequential = loop_characteristics.get('isSequential') == 'true' prefix = 'bpmn:multiInstanceLoopCharacteristics' cardinality = self.xpath(f'./{prefix}/bpmn:loopCardinality') loop_input = self.xpath(f'./{prefix}/bpmn:loopDataInputRef') if len(cardinality) == 0 and len(loop_input) == 0: - self.raise_validation_exception("A multiinstance task must specify a cardinality or a loop input data reference") + self.raise_validation_exception( + "A multiinstance task must specify a cardinality or a loop input data reference") elif len(cardinality) > 0 and len(loop_input) > 0: - self.raise_validation_exception("A multiinstance task must specify exactly one of cardinality or loop input data reference") + self.raise_validation_exception( + "A multiinstance task must specify exactly one of cardinality or loop input data reference") cardinality = cardinality[0].text if len(cardinality) > 0 else None loop_input = loop_input[0].text if len(loop_input) > 0 else None @@ -110,7 +116,7 @@ class TaskParser(NodeParser): if self.task.io_specification is not None: try: loop_input = [v for v in self.task.io_specification.data_inputs if v.name == loop_input][0] - except: + except Exception: self.raise_validation_exception('The loop input data reference is missing from the IO specification') else: loop_input = TaskDataReference(loop_input) @@ -125,7 +131,7 @@ class TaskParser(NodeParser): try: refs = set(self.task.io_specification.data_inputs + self.task.io_specification.data_outputs) loop_output = [v for v in refs if v.name == loop_output][0] - except: + except Exception: self.raise_validation_exception('The loop output data reference is missing from the IO specification') else: loop_output = TaskDataReference(loop_output) @@ -138,8 +144,8 @@ class TaskParser(NodeParser): original = self.spec.task_specs.pop(self.task.name) params = { - 'task_spec': '', - 'cardinality': cardinality, + 'task_spec': '', + 'cardinality': cardinality, 'data_input': loop_input, 'data_output':loop_output, 'input_item': input_item, @@ -155,7 +161,7 @@ class TaskParser(NodeParser): def _add_boundary_event(self, children): parent = _BoundaryEventParent( - self.spec, '%s.BoundaryEventParent' % self.get_id(), + self.spec, '%s.BoundaryEventParent' % self.bpmn_id, self.task, lane=self.task.lane) self.process_parser.parsed_nodes[self.node.get('id')] = parent parent.connect(self.task) @@ -176,10 +182,6 @@ class TaskParser(NodeParser): # Why do we just set random attributes willy nilly everywhere in the code???? # And we still pass around a gigantic kwargs dict whenever we create anything! self.task.extensions = self.parse_extensions() - self.task.documentation = self.parse_documentation() - # And now I have to add more of the same crappy thing. - self.task.data_input_associations = self.parse_incoming_data_references() - self.task.data_output_associations = self.parse_outgoing_data_references() io_spec = self.xpath('./bpmn:ioSpecification') if len(io_spec) > 0: @@ -193,30 +195,32 @@ class TaskParser(NodeParser): if len(mi_loop_characteristics) > 0: self._add_multiinstance_task(mi_loop_characteristics[0]) - boundary_event_nodes = self.doc_xpath('.//bpmn:boundaryEvent[@attachedToRef="%s"]' % self.get_id()) + boundary_event_nodes = self.doc_xpath('.//bpmn:boundaryEvent[@attachedToRef="%s"]' % self.bpmn_id) if boundary_event_nodes: parent = self._add_boundary_event(boundary_event_nodes) else: self.process_parser.parsed_nodes[self.node.get('id')] = self.task children = [] - outgoing = self.doc_xpath('.//bpmn:sequenceFlow[@sourceRef="%s"]' % self.get_id()) + outgoing = self.doc_xpath('.//bpmn:sequenceFlow[@sourceRef="%s"]' % self.bpmn_id) if len(outgoing) > 1 and not self.handles_multiple_outgoing(): self.raise_validation_exception('Multiple outgoing flows are not supported for tasks of type') for sequence_flow in outgoing: target_ref = sequence_flow.get('targetRef') try: target_node = one(self.doc_xpath('.//bpmn:*[@id="%s"]'% target_ref)) - except: + except Exception: self.raise_validation_exception('When looking for a task spec, we found two items, ' 'perhaps a form has the same ID? (%s)' % target_ref) c = self.process_parser.parse_node(target_node) - position = c.position + position = self.get_position(target_node) children.append((position, c, target_node, sequence_flow)) if children: # Sort children by their y coordinate. + # Why?? Isn't the point of parallel tasks that they can be executed in any order (or simultaneously)? + # And what if they're arranged horizontally? children = sorted(children, key=lambda tup: float(tup[0]["y"])) default_outgoing = self.node.get('default') @@ -238,17 +242,14 @@ class TaskParser(NodeParser): """ Returns a unique task spec name for this task (or the targeted one) """ - return target_ref or self.get_id() + return target_ref or self.bpmn_id def create_task(self): """ Create an instance of the task appropriately. A subclass can override this method to get extra information from the node. """ - return self.spec_class(self.spec, self.get_task_spec_name(), - lane=self.lane, - description=self.node.get('name', None), - position=self.position) + return self.spec_class(self.spec, self.bpmn_id, **self.bpmn_attributes) def connect_outgoing(self, outgoing_task, sequence_flow_node, is_default): """ diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/ValidationException.py b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/ValidationException.py index 78c98b328..212431ae8 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/ValidationException.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/ValidationException.py @@ -1,12 +1,13 @@ -# -*- coding: utf-8 -*- # Copyright (C) 2012 Matthew Hampton, 2023 Dan Funk # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/__init__.py b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/__init__.py index 9a31a4077..6bfe3719c 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/__init__.py @@ -1,12 +1,13 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2012 Matthew Hampton +# Copyright (C) 2012 Matthew Hampton, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/event_parsers.py b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/event_parsers.py index d98845047..de8578d46 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/event_parsers.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/event_parsers.py @@ -1,11 +1,28 @@ -from lxml import etree +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA -from SpiffWorkflow.bpmn.specs.events.event_definitions import CorrelationProperty +from lxml import etree from .ValidationException import ValidationException from .TaskParser import TaskParser from .util import first, one -from ..specs.events.event_definitions import ( +from ..specs.event_definitions import ( MultipleEventDefinition, TimeDateEventDefinition, DurationTimerEventDefinition, @@ -16,7 +33,8 @@ from ..specs.events.event_definitions import ( SignalEventDefinition, CancelEventDefinition, TerminateEventDefinition, - NoneEventDefinition + NoneEventDefinition, + CorrelationProperty ) CANCEL_EVENT_XPATH = './/bpmn:cancelEventDefinition' @@ -31,8 +49,26 @@ TIMER_EVENT_XPATH = './/bpmn:timerEventDefinition' class EventDefinitionParser(TaskParser): """This class provvides methods for parsing different event definitions.""" - def parse_cancel_event(self): - return CancelEventDefinition() + def __init__(self, process_parser, spec_class, node, nsmap=None, lane=None): + super().__init__(process_parser, spec_class, node, nsmap, lane) + self.event_nodes = [] + + def get_description(self): + spec_description = super().get_description() + if spec_description is not None: + if len(self.event_nodes) == 0: + event_description = 'Default' + elif len(self.event_nodes) > 1: + event_description = 'Multiple' + elif len(self.event_nodes) == 1: + event_description = self.process_parser.parser.spec_descriptions.get(self.event_nodes[0].tag) + return f'{event_description} {spec_description}' + + def get_event_description(self, event): + return self.process_parser.parser.spec_descriptions.get(event.tag) + + def parse_cancel_event(self, event): + return CancelEventDefinition(description=self.get_event_description(event)) def parse_error_event(self, error_event): """Parse the errorEventDefinition node and return an instance of ErrorEventDefinition.""" @@ -43,7 +79,7 @@ class EventDefinitionParser(TaskParser): name = error.get('name') else: name, error_code = 'None Error Event', None - return ErrorEventDefinition(name, error_code) + return ErrorEventDefinition(name, error_code, description=self.get_event_description(error_event)) def parse_escalation_event(self, escalation_event): """Parse the escalationEventDefinition node and return an instance of EscalationEventDefinition.""" @@ -55,7 +91,7 @@ class EventDefinitionParser(TaskParser): name = escalation.get('name') else: name, escalation_code = 'None Escalation Event', None - return EscalationEventDefinition(name, escalation_code) + return EscalationEventDefinition(name, escalation_code, description=self.get_event_description(escalation_event)) def parse_message_event(self, message_event): @@ -63,11 +99,13 @@ class EventDefinitionParser(TaskParser): if message_ref is not None: message = one(self.doc_xpath('.//bpmn:message[@id="%s"]' % message_ref)) name = message.get('name') + description = self.get_event_description(message_event) correlations = self.get_message_correlations(message_ref) else: name = message_event.getparent().get('name') + description = 'Message' correlations = {} - return MessageEventDefinition(name, correlations) + return MessageEventDefinition(name, correlations, description=description) def parse_signal_event(self, signal_event): """Parse the signalEventDefinition node and return an instance of SignalEventDefinition.""" @@ -78,26 +116,26 @@ class EventDefinitionParser(TaskParser): name = signal.get('name') else: name = signal_event.getparent().get('name') - return SignalEventDefinition(name) + return SignalEventDefinition(name, description=self.get_event_description(signal_event)) - def parse_terminate_event(self): + def parse_terminate_event(self, event): """Parse the terminateEventDefinition node and return an instance of TerminateEventDefinition.""" - return TerminateEventDefinition() + return TerminateEventDefinition(description=self.get_event_description(event)) - def parse_timer_event(self): + def parse_timer_event(self, event): """Parse the timerEventDefinition node and return an instance of TimerEventDefinition.""" - try: + description = self.get_event_description(event) name = self.node.get('name', self.node.get('id')) time_date = first(self.xpath('.//bpmn:timeDate')) if time_date is not None: - return TimeDateEventDefinition(name, time_date.text) + return TimeDateEventDefinition(name, time_date.text, description=description) time_duration = first(self.xpath('.//bpmn:timeDuration')) if time_duration is not None: - return DurationTimerEventDefinition(name, time_duration.text) + return DurationTimerEventDefinition(name, time_duration.text, description=description) time_cycle = first(self.xpath('.//bpmn:timeCycle')) if time_cycle is not None: - return CycleTimerEventDefinition(name, time_cycle.text) + return CycleTimerEventDefinition(name, time_cycle.text, description=description) raise ValidationException("Unknown Time Specification", node=self.node, file_name=self.filename) except Exception as e: raise ValidationException("Time Specification Error. " + str(e), node=self.node, file_name=self.filename) @@ -125,16 +163,14 @@ class EventDefinitionParser(TaskParser): if prop.name not in self.spec.correlation_keys[key]: self.spec.correlation_keys[key].append(prop.name) - kwargs = { - 'lane': self.lane, - 'description': self.node.get('name', None), - 'position': self.position, - } + kwargs = self.bpmn_attributes if cancel_activity is not None: kwargs['cancel_activity'] = cancel_activity + interrupt = 'Interrupting' if cancel_activity else 'Non-Interrupting' + kwargs['description'] = interrupt + ' ' + kwargs['description'] if parallel is not None: kwargs['parallel'] = parallel - return self.spec_class(self.spec, self.get_task_spec_name(), event_definition, **kwargs) + return self.spec_class(self.spec, self.bpmn_id, event_definition=event_definition, **kwargs) def get_event_definition(self, xpaths): """Returns all event definitions it can find in given list of xpaths""" @@ -142,29 +178,31 @@ class EventDefinitionParser(TaskParser): event_definitions = [] for path in xpaths: for event in self.xpath(path): + if event is not None: + self.event_nodes.append(event) if path == MESSAGE_EVENT_XPATH: event_definitions.append(self.parse_message_event(event)) elif path == SIGNAL_EVENT_XPATH: event_definitions.append(self.parse_signal_event(event)) elif path == TIMER_EVENT_XPATH: - event_definitions.append(self.parse_timer_event()) + event_definitions.append(self.parse_timer_event(event)) elif path == CANCEL_EVENT_XPATH: - event_definitions.append(self.parse_cancel_event()) + event_definitions.append(self.parse_cancel_event(event)) elif path == ERROR_EVENT_XPATH: event_definitions.append(self.parse_error_event(event)) elif path == ESCALATION_EVENT_XPATH: event_definitions.append(self.parse_escalation_event(event)) elif path == TERMINATION_EVENT_XPATH: - event_definitions.append(self.parse_terminate_event()) + event_definitions.append(self.parse_terminate_event(event)) parallel = self.node.get('parallelMultiple') == 'true' if len(event_definitions) == 0: - return NoneEventDefinition() + return NoneEventDefinition(description='Default') elif len(event_definitions) == 1: return event_definitions[0] else: - return MultipleEventDefinition(event_definitions, parallel) + return MultipleEventDefinition(event_definitions, parallel, description='Multiple') class StartEventParser(EventDefinitionParser): diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/node_parser.py b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/node_parser.py index 18c165d42..614d77871 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/node_parser.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/node_parser.py @@ -1,3 +1,22 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException from SpiffWorkflow.bpmn.specs.data_spec import TaskDataReference, BpmnIoSpecification from .util import first @@ -17,11 +36,25 @@ class NodeParser: self.nsmap = nsmap or DEFAULT_NSMAP self.filename = filename self.lane = self._get_lane() or lane - self.position = self._get_position() or {'x': 0.0, 'y': 0.0} - def get_id(self): + @property + def bpmn_id(self): return self.node.get('id') + @property + def bpmn_attributes(self): + return { + 'description': self.get_description(), + 'lane': self.lane, + 'bpmn_name': self.node.get('name'), + 'documentation': self.parse_documentation(), + 'data_input_associations': self.parse_incoming_data_references(), + 'data_output_associations': self.parse_outgoing_data_references(), + } + + def get_description(self): + return self.process_parser.parser.spec_descriptions.get(self.node.tag) + def xpath(self, xpath, extra_ns=None): return self._xpath(self.node, xpath, extra_ns) @@ -76,10 +109,10 @@ class NodeParser: data_refs = {} for elem in self.xpath('./bpmn:ioSpecification/bpmn:dataInput'): ref = self.create_data_spec(elem, TaskDataReference) - data_refs[ref.name] = ref + data_refs[ref.bpmn_id] = ref for elem in self.xpath('./bpmn:ioSpecification/bpmn:dataOutput'): ref = self.create_data_spec(elem, TaskDataReference) - data_refs[ref.name] = ref + data_refs[ref.bpmn_id] = ref inputs, outputs = [], [] for ref in self.xpath('./bpmn:ioSpecification/bpmn:inputSet/bpmn:dataInputRefs'): @@ -96,16 +129,20 @@ class NodeParser: def parse_extensions(self, node=None): return {} + def get_position(self, node=None): + node = node if node is not None else self.node + nodeid = node.get('id') + if nodeid is not None: + bounds = first(self.doc_xpath(f".//bpmndi:BPMNShape[@bpmnElement='{nodeid}']//dc:Bounds")) + if bounds is not None: + return {'x': float(bounds.get('x', 0)), 'y': float(bounds.get('y', 0))} + return {'x': 0.0, 'y': 0.0} + def _get_lane(self): - noderef = first(self.doc_xpath(f".//bpmn:flowNodeRef[text()='{self.get_id()}']")) + noderef = first(self.doc_xpath(f".//bpmn:flowNodeRef[text()='{self.bpmn_id}']")) if noderef is not None: return noderef.getparent().get('name') - def _get_position(self): - bounds = first(self.doc_xpath(f".//bpmndi:BPMNShape[@bpmnElement='{self.get_id()}']//dc:Bounds")) - if bounds is not None: - return {'x': float(bounds.get('x', 0)), 'y': float(bounds.get('y', 0))} - def _xpath(self, node, xpath, extra_ns=None): if extra_ns is not None: nsmap = self.nsmap.copy() diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/spec_description.py b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/spec_description.py new file mode 100644 index 000000000..ea4ea6158 --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/spec_description.py @@ -0,0 +1,52 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +from .util import full_tag + +# Having this configurable via the parser makes a lot more sense than requiring a subclass +# This can be further streamlined if we ever replace our parser + +SPEC_DESCRIPTIONS = { + full_tag('startEvent'): 'Start Event', + full_tag('endEvent'): 'End Event', + full_tag('userTask'): 'User Task', + full_tag('task'): 'Task', + full_tag('subProcess'): 'Subprocess', + full_tag('manualTask'): 'Manual Task', + full_tag('exclusiveGateway'): 'Exclusive Gateway', + full_tag('parallelGateway'): 'Parallel Gateway', + full_tag('inclusiveGateway'): 'Inclusive Gateway', + full_tag('callActivity'): 'Call Activity', + full_tag('transaction'): 'Transaction', + full_tag('scriptTask'): 'Script Task', + full_tag('serviceTask'): 'Service Task', + full_tag('intermediateCatchEvent'): 'Intermediate Catch Event', + full_tag('intermediateThrowEvent'): 'Intermediate Throw Event', + full_tag('boundaryEvent'): 'Boundary Event', + full_tag('receiveTask'): 'Receive Task', + full_tag('sendTask'): 'Send Task', + full_tag('eventBasedGateway'): 'Event Based Gateway', + full_tag('cancelEventDefinition'): 'Cancel', + full_tag('errorEventDefinition'): 'Error', + full_tag('escalationEventDefinition'): 'Escalation', + full_tag('terminateEventDefinition'): 'Terminate', + full_tag('messageEventDefinition'): 'Message', + full_tag('signalEventDefinition'): 'Signal', + full_tag('timerEventDefinition'): 'Timer', +} diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/task_parsers.py b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/task_parsers.py index 86affd455..effb4984f 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/task_parsers.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/task_parsers.py @@ -1,12 +1,13 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2012 Matthew Hampton +# Copyright (C) 2012 Matthew Hampton, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -85,13 +86,6 @@ class SubprocessParser: 'No "calledElement" attribute for Call Activity.', node=task_parser.node, file_name=task_parser.filename) - parser = task_parser.process_parser.parser.get_process_parser(called_element) - if parser is None: - raise ValidationException( - f"The process '{called_element}' was not found. Did you mean one of the following: " - f"{', '.join(task_parser.process_parser.parser.get_process_ids())}?", - node=task_parser.node, - file_name=task_parser.filename) return called_element @@ -99,10 +93,7 @@ class SubWorkflowParser(TaskParser): def create_task(self): subworkflow_spec = SubprocessParser.get_subprocess_spec(self) - return self.spec_class( - self.spec, self.get_task_spec_name(), subworkflow_spec, - lane=self.lane, position=self.position, - description=self.node.get('name', None)) + return self.spec_class(self.spec, self.bpmn_id, subworkflow_spec=subworkflow_spec, **self.bpmn_attributes) class CallActivityParser(TaskParser): @@ -110,23 +101,14 @@ class CallActivityParser(TaskParser): def create_task(self): subworkflow_spec = SubprocessParser.get_call_activity_spec(self) - return self.spec_class( - self.spec, self.get_task_spec_name(), subworkflow_spec, - lane=self.lane, position=self.position, - description=self.node.get('name', None)) + return self.spec_class(self.spec, self.bpmn_id, subworkflow_spec=subworkflow_spec, **self.bpmn_attributes) class ScriptTaskParser(TaskParser): - """ - Parses a script task - """ + """Parses a script task""" def create_task(self): - script = self.get_script() - return self.spec_class(self.spec, self.get_task_spec_name(), script, - lane=self.lane, - position=self.position, - description=self.node.get('name', None)) + return self.spec_class(self.spec, self.bpmn_id, script=self.get_script(), **self.bpmn_attributes) def get_script(self): """ @@ -138,6 +120,6 @@ class ScriptTaskParser(TaskParser): return one(self.xpath('.//bpmn:script')).text except AssertionError as ae: raise ValidationException( - f"Invalid Script Task. No Script Provided. " + str(ae), + "Invalid Script Task. No Script Provided. " + str(ae), node=self.node, file_name=self.filename) diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/util.py b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/util.py index 8ec4c886e..0dd876b9b 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/parser/util.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/parser/util.py @@ -1,12 +1,13 @@ -# -*- coding: utf-8 -*- # Copyright (C) 2012 Matthew Hampton # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/__init__.py b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/__init__.py index 9a31a4077..6bfe3719c 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/__init__.py @@ -1,12 +1,13 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2012 Matthew Hampton +# Copyright (C) 2012 Matthew Hampton, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/data_spec.py b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/data_spec.py index f81b9559d..0a9574e35 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/data_spec.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/data_spec.py @@ -1,3 +1,22 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from ..specs.data_spec import DataObject, TaskDataReference, BpmnIoSpecification from .helpers.spec import BpmnSpecConverter, BpmnDataSpecificationConverter diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/event_definition.py b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/event_definition.py index 84a7b620a..48407c87d 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/event_definition.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/event_definition.py @@ -1,6 +1,23 @@ -from .helpers.spec import EventDefinitionConverter +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA -from ..specs.events.event_definitions import ( +from SpiffWorkflow.bpmn.specs.event_definitions import ( CancelEventDefinition, ErrorEventDefinition, EscalationEventDefinition, @@ -13,6 +30,7 @@ from ..specs.events.event_definitions import ( CycleTimerEventDefinition, MultipleEventDefinition, ) +from .helpers.spec import EventDefinitionConverter class CancelEventDefinitionConverter(EventDefinitionConverter): def __init__(self, registry): diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/helpers/__init__.py b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/helpers/__init__.py index e69de29bb..b93acc8d9 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/helpers/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/helpers/__init__.py @@ -0,0 +1,18 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/helpers/dictionary.py b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/helpers/dictionary.py index b69788242..57691e11a 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/helpers/dictionary.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/helpers/dictionary.py @@ -1,3 +1,22 @@ +# Copyright (C) 2023 Elizabeth Esswein, Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from functools import partial class DictionaryConverter: diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/helpers/registry.py b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/helpers/registry.py index 3c5581b33..e2998cf5e 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/helpers/registry.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/helpers/registry.py @@ -1,3 +1,22 @@ +# Copyright (C) 2023 Elizabeth Esswein, Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from uuid import UUID from datetime import datetime, timedelta diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/helpers/spec.py b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/helpers/spec.py index 7e157a778..b8c224d12 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/helpers/spec.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/helpers/spec.py @@ -1,9 +1,32 @@ +# Copyright (C) 2023 Elizabeth Esswein, Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from functools import partial -from ...specs.BpmnSpecMixin import BpmnSpecMixin -from ...specs.events.event_definitions import NamedEventDefinition, TimerEventDefinition -from ...specs.events.event_definitions import CorrelationProperty -from ....operators import Attrib, PathAttrib +from SpiffWorkflow.operators import Attrib, PathAttrib + +from SpiffWorkflow.bpmn.specs.mixins.bpmn_spec_mixin import BpmnSpecMixin +from SpiffWorkflow.bpmn.specs.event_definitions import ( + NamedEventDefinition, + TimerEventDefinition, + CorrelationProperty +) class BpmnSpecConverter: @@ -57,7 +80,7 @@ class BpmnDataSpecificationConverter(BpmnSpecConverter): """ def to_dict(self, data_spec): - return { 'name': data_spec.name, 'description': data_spec.description } + return { 'bpmn_id': data_spec.bpmn_id, 'bpmn_name': data_spec.bpmn_name } def from_dict(self, dct): return self.spec_class(**dct) @@ -72,7 +95,11 @@ class EventDefinitionConverter(BpmnSpecConverter): """ def to_dict(self, event_definition): - dct = {'internal': event_definition.internal, 'external': event_definition.external} + dct = { + 'internal': event_definition.internal, + 'external': event_definition.external, + 'description': event_definition.description, + } if isinstance(event_definition, (NamedEventDefinition, TimerEventDefinition)): dct['name'] = event_definition.name return dct @@ -105,7 +132,7 @@ class TaskSpecConverter(BpmnSpecConverter): modules of this package; the `camunda`,`dmn`, and `spiff` serialization packages contain other examples. """ - def get_default_attributes(self, spec, include_data=False): + def get_default_attributes(self, spec): """Extracts the default Spiff attributes from a task spec. :param spec: the task spec to be converted @@ -113,38 +140,17 @@ class TaskSpecConverter(BpmnSpecConverter): Returns: a dictionary of standard task spec attributes """ - dct = { - 'id': spec.id, + return { 'name': spec.name, 'description': spec.description, 'manual': spec.manual, - 'internal': spec.internal, 'lookahead': spec.lookahead, 'inputs': [task.name for task in spec.inputs], 'outputs': [task.name for task in spec.outputs], - } - # This stuff is also all defined in the base task spec, but can contain data, so we need - # our data serializer. I think we should try to get this stuff out of the base task spec. - if include_data: - dct['data'] = self.registry.convert(spec.data) - dct['defines'] = self.registry.convert(spec.defines) - dct['pre_assign'] = self.registry.convert(spec.pre_assign) - dct['post_assign'] = self.registry.convert(spec.post_assign) - - return dct - - def get_bpmn_attributes(self, spec): - """Extracts the attributes added by the `BpmnSpecMixin` class. - - :param spec: the task spec to be converted - - Returns: - a dictionary of BPMN task spec attributes - """ - return { + 'bpmn_id': spec.bpmn_id, + 'bpmn_name': spec.bpmn_name, 'lane': spec.lane, 'documentation': spec.documentation, - 'position': spec.position, 'data_input_associations': [ self.registry.convert(obj) for obj in spec.data_input_associations ], 'data_output_associations': [ self.registry.convert(obj) for obj in spec.data_output_associations ], 'io_specification': self.registry.convert(spec.io_specification), @@ -189,38 +195,36 @@ class TaskSpecConverter(BpmnSpecConverter): 'test_before': spec.test_before, } - def task_spec_from_dict(self, dct, include_data=False): + def task_spec_from_dict(self, dct): """ Creates a task spec based on the supplied dictionary. It handles setting the default task spec attributes as well as attributes added by `BpmnSpecMixin`. :param dct: the dictionary to create the task spec from - :param include_data: whether or not to include task spec data attributes Returns: a restored task spec """ - internal = dct.pop('internal') + dct['data_input_associations'] = self.registry.restore(dct.pop('data_input_associations', [])) + dct['data_output_associations'] = self.registry.restore(dct.pop('data_output_associations', [])) + inputs = dct.pop('inputs') outputs = dct.pop('outputs') - spec = self.spec_class(**dct) - spec.internal = internal + wf_spec = dct.pop('wf_spec') + name = dct.pop('name') + bpmn_id = dct.pop('bpmn_id') + + spec = self.spec_class(wf_spec, name, **dct) spec.inputs = inputs spec.outputs = outputs - spec.id = dct['id'] - if include_data: - spec.data = self.registry.restore(dct.get('data', {})) - spec.defines = self.registry.restore(dct.get('defines', {})) - spec.pre_assign = self.registry.restore(dct.get('pre_assign', {})) - spec.post_assign = self.registry.restore(dct.get('post_assign', {})) + if issubclass(self.spec_class, BpmnSpecMixin) and bpmn_id != name: + # This is a hack for multiinstance tasks :( At least it is simple. + # Ideally I'd fix it in the parser, but I'm afraid of quickly running into a wall there + spec.bpmn_id = bpmn_id if isinstance(spec, BpmnSpecMixin): - spec.documentation = dct.pop('documentation', None) - spec.lane = dct.pop('lane', None) - spec.data_input_associations = self.registry.restore(dct.pop('data_input_associations', [])) - spec.data_output_associations = self.registry.restore(dct.pop('data_output_associations', [])) spec.io_specification = self.registry.restore(dct.pop('io_specification', None)) return spec diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/migration/exceptions.py b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/migration/exceptions.py index 579038ad3..e4df809ed 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/migration/exceptions.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/migration/exceptions.py @@ -1,3 +1,22 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from SpiffWorkflow.exceptions import WorkflowException class VersionMigrationError(WorkflowException): diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/migration/version_1_1.py b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/migration/version_1_1.py index 35b0b6e67..2802f5214 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/migration/version_1_1.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/migration/version_1_1.py @@ -1,3 +1,22 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + def move_subprocesses_to_top(dct): subprocesses = dict((sp, { 'tasks': {}, 'root': None, 'data': {}, 'success': True }) for sp in dct['subprocesses']) diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/migration/version_1_2.py b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/migration/version_1_2.py index 079120e01..4020cb0af 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/migration/version_1_2.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/migration/version_1_2.py @@ -1,7 +1,26 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from datetime import datetime, timedelta from SpiffWorkflow.task import TaskState -from SpiffWorkflow.bpmn.specs.events.event_definitions import LOCALTZ +from SpiffWorkflow.bpmn.specs.event_definitions import LOCALTZ from .exceptions import VersionMigrationError @@ -29,7 +48,7 @@ def convert_timer_expressions(dct): elif isinstance(dt, timedelta): spec['event_definition']['expression'] = f"'{td_to_iso(dt)}'" spec['event_definition']['typename'] = 'DurationTimerEventDefinition' - except: + except Exception: raise VersionMigrationError(message.format(spec=spec['name'])) def convert_cycle(spec, task): @@ -47,7 +66,7 @@ def convert_timer_expressions(dct): 'next': datetime.combine(dt.date(), dt.time(), LOCALTZ).isoformat(), 'duration': duration.total_seconds(), } - except: + except Exception: raise VersionMigrationError(message.format(spec=spec['name'])) if spec['typename'] == 'StartEvent': @@ -65,7 +84,8 @@ def convert_timer_expressions(dct): task['children'].remove(remove['id']) dct['tasks'].pop(remove['id']) - has_timer = lambda ts: 'event_definition' in ts and ts['event_definition']['typename'] in [ 'CycleTimerEventDefinition', 'TimerEventDefinition'] + def has_timer(ts): + return "event_definition" in ts and ts["event_definition"]["typename"] in ["CycleTimerEventDefinition", "TimerEventDefinition"] for spec in [ ts for ts in dct['spec']['task_specs'].values() if has_timer(ts) ]: spec['event_definition']['name'] = spec['event_definition'].pop('label') if spec['event_definition']['typename'] == 'TimerEventDefinition': @@ -113,7 +133,7 @@ def create_data_objects_and_io_specs(dct): item['typename'] = 'DataObject' def check_multiinstance(dct): - + specs = [ spec for spec in dct['spec']['task_specs'].values() if 'prevtaskclass' in spec ] if len(specs) > 0: raise VersionMigrationError("This workflow cannot be migrated because it contains MultiInstance Tasks") @@ -143,3 +163,97 @@ def update_task_states(dct): update(dct) for sp in dct['subprocesses'].values(): update(sp) + +def convert_simple_tasks(dct): + + def update_specs(task_specs): + for name, spec in task_specs.items(): + if spec['typename'] == 'StartTask': + spec['typename'] = 'BpmnStartTask' + elif spec['typename'] == 'Simple': + spec['typename'] = 'SimpleBpmnTask' + + update_specs(dct['spec']['task_specs']) + for subprocess_spec in dct['subprocess_specs'].values(): + update_specs(subprocess_spec['task_specs']) + +def update_bpmn_attributes(dct): + + descriptions = { + 'StartEvent': 'Start Event', + 'EndEvent': 'End Event', + 'UserTask': 'User Task', + 'Task': 'Task', + 'SubProcess': 'Subprocess', + 'ManualTask': 'Manual Task', + 'ExclusiveGateway': 'Exclusive Gateway', + 'ParallelGateway': 'Parallel Gateway', + 'InclusiveGateway': 'Inclusive Gateway', + 'CallActivity': 'Call Activity', + 'TransactionSubprocess': 'Transaction', + 'ScriptTask': 'Script Task', + 'ServiceTask': 'Service Task', + 'IntermediateCatchEvent': 'Intermediate Catch Event', + 'IntermediateThrowEvent': 'Intermediate Throw Event', + 'BoundaryEvent': 'Boundary Event', + 'ReceiveTask': 'Receive Task', + 'SendTask': 'Send Task', + 'EventBasedGateway': 'Event Based Gateway', + 'CancelEventDefinition': 'Cancel', + 'ErrorEventDefinition': 'Error', + 'EscalationEventDefinition': 'Escalation', + 'TerminateEventDefinition': 'Terminate', + 'MessageEventDefinition': 'Message', + 'SignalEventDefinition': 'Signal', + 'TimerEventDefinition': 'Timer', + 'NoneEventDefinition': 'Default', + 'MultipleEventDefinition': 'Multiple' + } + + def update_data_spec(obj): + obj['bpmn_id'] = obj.pop('name') + obj['bpmn_name'] = obj.pop('description', None) + + def update_io_spec(io_spec): + for obj in io_spec['data_inputs']: + update_data_spec(obj) + for obj in io_spec['data_outputs']: + update_data_spec(obj) + + def update_task_specs(spec): + for spec in spec['task_specs'].values(): + spec['bpmn_id'] = None + if spec['typename'] not in ['BpmnStartTask', 'SimpleBpmnTask', '_EndJoin', '_BoundaryEventParent']: + spec['bpmn_id'] = spec['name'] + spec['bpmn_name'] = spec['description'] or None + if 'event_definition' in spec and spec['event_definition']['typename'] in descriptions: + spec_desc = descriptions.get(spec['typename']) + event_desc = descriptions.get(spec['event_definition']['typename']) + cancelling = spec.get('cancel_activity') + interrupt = 'Interrupting ' if cancelling else 'Non-Interrupting ' if not cancelling else '' + desc = f'{interrupt}{event_desc} {spec_desc}' + elif spec['typename'] in descriptions: + desc = descriptions.get(spec['typename']) + else: + desc = None + spec['description'] = desc + else: + spec['bpmn_name'] = None + spec['description'] = None + if spec.get('io_specification') is not None: + update_io_spec(spec['io_specification']) + for obj in spec.get('data_input_associations', []): + update_data_spec(obj) + for obj in spec.get('data_output_associations', []): + update_data_spec(obj) + + update_task_specs(dct['spec']) + for obj in dct['spec'].get('data_objects', {}).values(): + update_data_spec(obj) + + for subprocess_spec in dct['subprocess_specs'].values(): + update_task_specs(subprocess_spec) + for obj in subprocess_spec.get('data_objects', {}).values(): + update_data_spec(obj) + if subprocess_spec.get('io_specification') is not None: + update_io_spec(subprocess_spec['io_specification']) diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/migration/version_migration.py b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/migration/version_migration.py index d10fcf0ee..c0efb7f04 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/migration/version_migration.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/migration/version_migration.py @@ -1,3 +1,22 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from copy import deepcopy from .version_1_1 import move_subprocesses_to_top @@ -8,6 +27,8 @@ from .version_1_2 import ( check_multiinstance, remove_loop_reset, update_task_states, + convert_simple_tasks, + update_bpmn_attributes, ) def from_version_1_1(old): @@ -38,6 +59,8 @@ def from_version_1_1(old): check_multiinstance(new) remove_loop_reset(new) update_task_states(new) + convert_simple_tasks(new) + update_bpmn_attributes(new) new['VERSION'] = "1.2" return new diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/process_spec.py b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/process_spec.py index 6255b1279..7ee43f833 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/process_spec.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/process_spec.py @@ -1,5 +1,24 @@ -from ..specs.BpmnProcessSpec import BpmnProcessSpec -from ..specs.events.IntermediateEvent import _BoundaryEventParent +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +from SpiffWorkflow.bpmn.specs.bpmn_process_spec import BpmnProcessSpec +from SpiffWorkflow.bpmn.specs.control import _BoundaryEventParent from .helpers.spec import WorkflowSpecConverter @@ -39,12 +58,9 @@ class BpmnProcessSpecConverter(WorkflowSpecConverter): def from_dict(self, dct): spec = self.spec_class(name=dct['name'], description=dct['description'], filename=dct['file']) - # There a nostart arg in the base workflow spec class that prevents start task creation, but - # the BPMN process spec doesn't pass it in, so we have to delete the auto generated Start task. + # These are automatically created with a workflow and should be replaced del spec.task_specs['Start'] spec.start = None - - # These are also automatically created with a workflow and should be replaced del spec.task_specs['End'] del spec.task_specs[f'{spec.name}.EndJoin'] @@ -60,6 +76,7 @@ class BpmnProcessSpecConverter(WorkflowSpecConverter): # Add messaging related stuff spec.correlation_keys = dct.pop('correlation_keys', {}) + dct['task_specs'].pop('Root', None) for name, task_dict in dct['task_specs'].items(): # I hate this, but I need to pass in the workflow spec when I create the task. # IMO storing the workflow spec on the task spec is a TERRIBLE idea, but that's diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/task_spec.py b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/task_spec.py index 894debc34..d360f5e76 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/task_spec.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/task_spec.py @@ -1,68 +1,75 @@ -from .helpers.spec import TaskSpecConverter +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA -from ...specs.StartTask import StartTask -from ...specs.Simple import Simple - -from ..specs.BpmnProcessSpec import _EndJoin -from ..specs.BpmnSpecMixin import _BpmnCondition -from ..specs.NoneTask import NoneTask -from ..specs.UserTask import UserTask -from ..specs.ManualTask import ManualTask -from ..specs.ScriptTask import ScriptTask -from ..specs.MultiInstanceTask import StandardLoopTask, SequentialMultiInstanceTask, ParallelMultiInstanceTask -from ..specs.SubWorkflowTask import CallActivity, TransactionSubprocess, SubWorkflowTask -from ..specs.ExclusiveGateway import ExclusiveGateway -from ..specs.InclusiveGateway import InclusiveGateway -from ..specs.ParallelGateway import ParallelGateway -from ..specs.events.StartEvent import StartEvent -from ..specs.events.EndEvent import EndEvent -from ..specs.events.IntermediateEvent import ( - BoundaryEvent, - _BoundaryEventParent, - EventBasedGateway, +from SpiffWorkflow.bpmn.specs.control import BpmnStartTask, _EndJoin, _BoundaryEventParent, SimpleBpmnTask +from SpiffWorkflow.bpmn.specs.bpmn_task_spec import _BpmnCondition +from SpiffWorkflow.bpmn.specs.defaults import ( + UserTask, + ManualTask, + NoneTask, + ScriptTask, + ExclusiveGateway, + InclusiveGateway, + ParallelGateway, + StandardLoopTask, + SequentialMultiInstanceTask, + ParallelMultiInstanceTask, + CallActivity, + TransactionSubprocess, + SubWorkflowTask, + StartEvent, + EndEvent, IntermediateCatchEvent, IntermediateThrowEvent, + BoundaryEvent, + EventBasedGateway, SendTask, ReceiveTask, ) - -class DefaultTaskSpecConverter(TaskSpecConverter): - - def to_dict(self, spec): - dct = self.get_default_attributes(spec) - return dct - - def from_dict(self, dct): - return self.task_spec_from_dict(dct) - - -class SimpleTaskConverter(DefaultTaskSpecConverter): - def __init__(self, registry): - super().__init__(Simple, registry) - - -class StartTaskConverter(DefaultTaskSpecConverter): - def __init__(self, registry): - super().__init__(StartTask, registry) - - -class EndJoinConverter(DefaultTaskSpecConverter): - def __init__(self, registry): - super().__init__(_EndJoin, registry) +from .helpers.spec import TaskSpecConverter class BpmnTaskSpecConverter(TaskSpecConverter): def to_dict(self, spec): dct = self.get_default_attributes(spec) - dct.update(self.get_bpmn_attributes(spec)) return dct def from_dict(self, dct): return self.task_spec_from_dict(dct) +class SimpleBpmnTaskConverter(BpmnTaskSpecConverter): + def __init__(self, registry): + super().__init__(SimpleBpmnTask, registry) + +class BpmnStartTaskConverter(BpmnTaskSpecConverter): + def __init__(self, registry): + super().__init__(BpmnStartTask, registry) + +class EndJoinConverter(BpmnTaskSpecConverter): + def __init__(self, registry): + super().__init__(_EndJoin, registry) + + + class NoneTaskConverter(BpmnTaskSpecConverter): def __init__(self, registry): super().__init__(NoneTask, registry) @@ -85,7 +92,6 @@ class ScriptTaskConverter(BpmnTaskSpecConverter): def to_dict(self, spec): dct = self.get_default_attributes(spec) - dct.update(self.get_bpmn_attributes(spec)) dct['script'] = spec.script return dct @@ -97,7 +103,6 @@ class StandardLoopTaskConverter(BpmnTaskSpecConverter): def to_dict(self, spec): dct = self.get_default_attributes(spec) - dct.update(self.get_bpmn_attributes(spec)) dct.update(self.get_standard_loop_attributes(spec)) return dct @@ -106,7 +111,6 @@ class MultiInstanceTaskConverter(BpmnTaskSpecConverter): def to_dict(self, spec): dct = self.get_default_attributes(spec) - dct.update(self.get_bpmn_attributes(spec)) dct['task_spec'] = spec.task_spec dct['cardinality'] = spec.cardinality dct['data_input'] = self.registry.convert(spec.data_input) @@ -294,8 +298,8 @@ class EventBasedGatewayConverter(EventConverter): DEFAULT_TASK_SPEC_CONVERTER_CLASSES = [ - SimpleTaskConverter, - StartTaskConverter, + SimpleBpmnTaskConverter, + BpmnStartTaskConverter, EndJoinConverter, NoneTaskConverter, UserTaskConverter, diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/workflow.py b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/workflow.py index 737180a26..396beaec6 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/workflow.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/serializer/workflow.py @@ -1,11 +1,30 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + import json import gzip from copy import deepcopy from uuid import UUID -from ..workflow import BpmnMessage, BpmnWorkflow -from ..specs.SubWorkflowTask import SubWorkflowTask -from ...task import Task +from SpiffWorkflow.task import Task +from SpiffWorkflow.bpmn.workflow import BpmnMessage, BpmnWorkflow +from SpiffWorkflow.bpmn.specs.mixins.subworkflow_task import SubWorkflowTask from .migration.version_migration import MIGRATIONS from .helpers.registry import DefaultRegistry @@ -134,7 +153,7 @@ class BpmnWorkflowSerializer: dct = self.__get_dict(serialization, use_gzip) if self.VERSION_KEY in dct: return dct[self.VERSION_KEY] - except: # Don't bail out trying to get a version, just return none. + except Exception: # Don't bail out trying to get a version, just return none. return None def workflow_to_dict(self, workflow): @@ -260,7 +279,7 @@ class BpmnWorkflowSerializer: for child_task_id in task_dict['children']: if child_task_id in process_dct['tasks']: - child = process_dct['tasks'][child_task_id] + process_dct['tasks'][child_task_id] self.task_tree_from_dict(process_dct, child_task_id, task, process, top, top_dct) else: raise ValueError(f"Task {task_id} ({task_spec.name}) has child {child_task_id}, but no such task exists") diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/BpmnProcessSpec.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/BpmnProcessSpec.py deleted file mode 100644 index 90faa5ac5..000000000 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/BpmnProcessSpec.py +++ /dev/null @@ -1,78 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2012 Matthew Hampton -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 USA - -from ...task import TaskState -from .UnstructuredJoin import UnstructuredJoin -from ...specs.Simple import Simple -from ...specs.WorkflowSpec import WorkflowSpec - - -class _EndJoin(UnstructuredJoin): - - def _check_threshold_unstructured(self, my_task, force=False): - # Look at the tree to find all ready and waiting tasks (excluding - # ourself). The EndJoin waits for everyone! - waiting_tasks = [] - for task in my_task.workflow.get_tasks(TaskState.READY | TaskState.WAITING): - if task.thread_id != my_task.thread_id: - continue - if task.task_spec == my_task.task_spec: - continue - - is_mine = False - w = task.workflow - if w == my_task.workflow: - is_mine = True - while w and w.outer_workflow != w: - w = w.outer_workflow - if w == my_task.workflow: - is_mine = True - if is_mine: - waiting_tasks.append(task) - - return force or len(waiting_tasks) == 0, waiting_tasks - - def _run_hook(self, my_task): - result = super(_EndJoin, self)._run_hook(my_task) - my_task.workflow.data.update(my_task.data) - return result - - -class BpmnProcessSpec(WorkflowSpec): - """ - This class represents the specification of a BPMN process workflow. This - specialises the standard Spiff WorkflowSpec class with a few extra methods - and attributes. - """ - - def __init__(self, name=None, description=None, filename=None, svg=None): - """ - Constructor. - - :param svg: This provides the SVG representation of the workflow as an - LXML node. (optional) - """ - super(BpmnProcessSpec, self).__init__(name=name, filename=filename) - self.end = _EndJoin(self, '%s.EndJoin' % (self.name)) - self.end.connect(Simple(self, 'End')) - self.svg = svg - self.description = description - self.io_specification = None - self.data_objects = {} - self.data_stores = {} - self.correlation_keys = {} diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/BpmnSpecMixin.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/BpmnSpecMixin.py deleted file mode 100644 index 086610365..000000000 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/BpmnSpecMixin.py +++ /dev/null @@ -1,113 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2012 Matthew Hampton -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 USA - -from ..exceptions import WorkflowDataException -from ...operators import Operator -from ...specs.base import TaskSpec - - -class _BpmnCondition(Operator): - - def __init__(self, *args): - if len(args) > 1: - raise TypeError("Too many arguments") - super(_BpmnCondition, self).__init__(*args) - - def _matches(self, task): - return task.workflow.script_engine.evaluate(task, self.args[0]) - - -class BpmnSpecMixin(TaskSpec): - """ - All BPMN spec classes should mix this superclass in. It adds a number of - methods that are BPMN specific to the TaskSpec. - """ - - def __init__(self, wf_spec, name, lane=None, position=None, **kwargs): - """ - Constructor. - - :param lane: Indicates the name of the lane that this task belongs to - (optional). - """ - super(BpmnSpecMixin, self).__init__(wf_spec, name, **kwargs) - self.lane = lane - self.position = position or {'x': 0, 'y': 0} - self.documentation = None - self.data_input_associations = [] - self.data_output_associations = [] - self.io_specification = None - - @property - def spec_type(self): - return 'BPMN Task' - - def connect_outgoing_if(self, condition, taskspec): - """ - Connect this task spec to the indicated child, if the condition - evaluates to true. This should only be called if the task has a - connect_if method (e.g. ExclusiveGateway). - """ - if condition is None: - self.connect(taskspec) - else: - self.connect_if(_BpmnCondition(condition), taskspec) - - def _update_hook(self, my_task): - - super()._update_hook(my_task) - # This copies data from data objects - for obj in self.data_input_associations: - obj.get(my_task) - - # If an IO spec was given, require all inputs are present, and remove all other inputs. - if self.io_specification is not None and len(self.io_specification.data_inputs) > 0: - data = {} - for var in self.io_specification.data_inputs: - if var.name not in my_task.data: - raise WorkflowDataException(f"Missing data input", task=my_task, data_input=var) - data[var.name] = my_task.data[var.name] - my_task.data = data - - return True - - def _on_complete_hook(self, my_task): - - if isinstance(my_task.parent.task_spec, BpmnSpecMixin): - my_task.parent.task_spec._child_complete_hook(my_task) - - if self.io_specification is not None and len(self.io_specification.data_outputs) > 0: - data = {} - for var in self.io_specification.data_outputs: - if var.name not in my_task.data: - raise WorkflowDataException(f"Missing data ouput", task=my_task, data_output=var) - data[var.name] = my_task.data[var.name] - my_task.data = data - - for obj in self.data_output_associations: - obj.set(my_task) - - for obj in self.data_input_associations: - # Remove the any copied input variables that might not have already been removed - my_task.data.pop(obj.name, None) - - super(BpmnSpecMixin, self)._on_complete_hook(my_task) - - def _child_complete_hook(self, child_task): - pass diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/ServiceTask.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/ServiceTask.py deleted file mode 100644 index 234ec374f..000000000 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/ServiceTask.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- - -from .ScriptTask import ScriptEngineTask - - -class ServiceTask(ScriptEngineTask): - - """ - Task Spec for a bpmn:serviceTask node. - """ - - def __init__(self, wf_spec, name, **kwargs): - super(ServiceTask, self).__init__(wf_spec, name, **kwargs) - - @property - def spec_type(self): - return 'Service Task' diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/__init__.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/__init__.py index 9a31a4077..21597e92d 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/__init__.py @@ -1,12 +1,13 @@ -# -*- coding: utf-8 -*- # Copyright (C) 2012 Matthew Hampton # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/bpmn_process_spec.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/bpmn_process_spec.py new file mode 100644 index 000000000..9dcd4505e --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/bpmn_process_spec.py @@ -0,0 +1,50 @@ +# Copyright (C) 2012 Matthew Hampton, 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +from SpiffWorkflow.specs.WorkflowSpec import WorkflowSpec +from SpiffWorkflow.bpmn.specs.control import _EndJoin, BpmnStartTask, SimpleBpmnTask + + +class BpmnProcessSpec(WorkflowSpec): + """ + This class represents the specification of a BPMN process workflow. This + specialises the standard Spiff WorkflowSpec class with a few extra methods + and attributes. + """ + + def __init__(self, name=None, description=None, filename=None, svg=None): + """ + Constructor. + + :param svg: This provides the SVG representation of the workflow as an + LXML node. (optional) + """ + super(BpmnProcessSpec, self).__init__(name=name, filename=filename, nostart=True) + # Add a root task to ensure all tasks in the workflow are bpmn tasks + # The serializer ignores this task + SimpleBpmnTask(self, 'Root') + self.start = BpmnStartTask(self, 'Start') + self.end = _EndJoin(self, '%s.EndJoin' % (self.name)) + self.end.connect(SimpleBpmnTask(self, 'End')) + self.svg = svg + self.description = description + self.io_specification = None + self.data_objects = {} + self.data_stores = {} + self.correlation_keys = {} diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/bpmn_task_spec.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/bpmn_task_spec.py new file mode 100644 index 000000000..ecfda9b61 --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/bpmn_task_spec.py @@ -0,0 +1,101 @@ + +from SpiffWorkflow.bpmn.exceptions import WorkflowDataException +from SpiffWorkflow.operators import Operator +from SpiffWorkflow.specs.base import TaskSpec + + +class _BpmnCondition(Operator): + + def __init__(self, *args): + if len(args) > 1: + raise TypeError("Too many arguments") + super(_BpmnCondition, self).__init__(*args) + + def _matches(self, task): + return task.workflow.script_engine.evaluate(task, self.args[0], external_methods=task.workflow.data) + + +class BpmnTaskSpec(TaskSpec): + """ + This class provides BPMN-specific attributes. + + It is intended to be used with all tasks in a BPMN workflow. Spiff internal tasks (such + as Root, EndJoin, etc) inherit directly from this. + + Visible tasks inherit from `BpmnSpecMixin`, which will assign the `bpmn_id` and `bpmn_name`. + + The intent is to (1) give all tasks in the workflow the same attributes and (2) provide an + easy way of knowing whether a task appearson the diagram. + """ + def __init__(self, wf_spec, name, lane=None, documentation=None, + data_input_associations=None, data_output_associations=None, **kwargs): + """ + :param lane: Indicates the name of the lane that this task belongs to + :param documentation: the contents of the documentation element + :param data_input_associations: a list of data references to be used as inputs to the task + :param data_output_associations: a list of data references to be used as inputs to the task + """ + super().__init__(wf_spec, name, **kwargs) + self.bpmn_id = None + self.bpmn_name = None + self.lane = lane + self.documentation = documentation + self.data_input_associations = data_input_associations or [] + self.data_output_associations = data_output_associations or [] + self.io_specification = None + if self.description is None: + self.description = 'BPMN Task' + + def connect_outgoing_if(self, condition, taskspec): + """ + Connect this task spec to the indicated child, if the condition + evaluates to true. This should only be called if the task has a + connect_if method (e.g. ExclusiveGateway). + """ + if condition is None: + self.connect(taskspec) + else: + self.connect_if(_BpmnCondition(condition), taskspec) + + def _update_hook(self, my_task): + + super()._update_hook(my_task) + # This copies data from data objects + for obj in self.data_input_associations: + obj.get(my_task) + + # If an IO spec was given, require all inputs are present, and remove all other inputs. + if self.io_specification is not None and len(self.io_specification.data_inputs) > 0: + data = {} + for var in self.io_specification.data_inputs: + if var.bpmn_id not in my_task.data: + raise WorkflowDataException("Missing data input", task=my_task, data_input=var) + data[var.bpmn_id] = my_task.data[var.bpmn_id] + my_task.data = data + + return True + + def _on_complete_hook(self, my_task): + + if my_task.parent: + my_task.parent.task_spec._child_complete_hook(my_task) + + if self.io_specification is not None and len(self.io_specification.data_outputs) > 0: + data = {} + for var in self.io_specification.data_outputs: + if var.bpmn_id not in my_task.data: + raise WorkflowDataException("Missing data ouput", task=my_task, data_output=var) + data[var.bpmn_id] = my_task.data[var.bpmn_id] + my_task.data = data + + for obj in self.data_output_associations: + obj.set(my_task) + + for obj in self.data_input_associations: + # Remove the any copied input variables that might not have already been removed + my_task.data.pop(obj.bpmn_id, None) + + super()._on_complete_hook(my_task) + + def _child_complete_hook(self, child_task): + pass diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/events/IntermediateEvent.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/control.py similarity index 50% rename from SpiffWorkflow/SpiffWorkflow/bpmn/specs/events/IntermediateEvent.py rename to SpiffWorkflow/SpiffWorkflow/bpmn/specs/control.py index 2eb43e9a1..7b512c0f6 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/events/IntermediateEvent.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/control.py @@ -1,13 +1,14 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2012 Matthew Hampton +# Copyright (C) 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -17,40 +18,20 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -from .event_types import ThrowingEvent, CatchingEvent -from ..BpmnSpecMixin import BpmnSpecMixin -from ....specs.Simple import Simple -from ....task import TaskState - -class SendTask(ThrowingEvent): - - @property - def spec_type(self): - return 'Send Task' +from SpiffWorkflow.task import TaskState +from SpiffWorkflow.specs.StartTask import StartTask +from SpiffWorkflow.bpmn.specs.bpmn_task_spec import BpmnTaskSpec +from SpiffWorkflow.bpmn.specs.mixins.unstructured_join import UnstructuredJoin +from SpiffWorkflow.bpmn.specs.mixins.events.intermediate_event import BoundaryEvent -class ReceiveTask(CatchingEvent): +class BpmnStartTask(BpmnTaskSpec, StartTask): + pass - @property - def spec_type(self): - return 'Receive Task' +class SimpleBpmnTask(BpmnTaskSpec): + pass - -class IntermediateCatchEvent(CatchingEvent): - - @property - def spec_type(self): - return f'{self.event_definition.event_type} Catching Event' - - -class IntermediateThrowEvent(ThrowingEvent): - - @property - def spec_type(self): - return f'{self.event_definition.event_type} Throwing Event' - - -class _BoundaryEventParent(Simple, BpmnSpecMixin): +class _BoundaryEventParent(BpmnTaskSpec): """This task is inserted before a task with boundary events.""" # I wonder if this would be better modelled as some type of join. @@ -58,8 +39,7 @@ class _BoundaryEventParent(Simple, BpmnSpecMixin): # they're attached to be inputs rather than outputs. def __init__(self, wf_spec, name, main_child_task_spec, **kwargs): - - super(_BoundaryEventParent, self).__init__(wf_spec, name) + super(_BoundaryEventParent, self).__init__(wf_spec, name, **kwargs) self.main_child_task_spec = main_child_task_spec @property @@ -93,39 +73,32 @@ class _BoundaryEventParent(Simple, BpmnSpecMixin): child._set_state(state) -class BoundaryEvent(CatchingEvent): - """Task Spec for a bpmn:boundaryEvent node.""" +class _EndJoin(UnstructuredJoin, BpmnTaskSpec): - def __init__(self, wf_spec, name, event_definition, cancel_activity, **kwargs): - """ - Constructor. + def _check_threshold_unstructured(self, my_task, force=False): + # Look at the tree to find all ready and waiting tasks (excluding + # ourself). The EndJoin waits for everyone! + waiting_tasks = [] + for task in my_task.workflow.get_tasks(TaskState.READY | TaskState.WAITING): + if task.thread_id != my_task.thread_id: + continue + if task.task_spec == my_task.task_spec: + continue - :param cancel_activity: True if this is a Cancelling boundary event. - """ - super(BoundaryEvent, self).__init__(wf_spec, name, event_definition, **kwargs) - self.cancel_activity = cancel_activity + is_mine = False + w = task.workflow + if w == my_task.workflow: + is_mine = True + while w and w.outer_workflow != w: + w = w.outer_workflow + if w == my_task.workflow: + is_mine = True + if is_mine: + waiting_tasks.append(task) - @property - def spec_type(self): - interrupting = 'Interrupting' if self.cancel_activity else 'Non-Interrupting' - return f'{interrupting} {self.event_definition.event_type} Event' + return force or len(waiting_tasks) == 0, waiting_tasks - def catches(self, my_task, event_definition, correlations=None): - # Boundary events should only be caught while waiting - return super(BoundaryEvent, self).catches(my_task, event_definition, correlations) and my_task.state == TaskState.WAITING - - -class EventBasedGateway(CatchingEvent): - - @property - def spec_type(self): - return 'Event Based Gateway' - - def _predict_hook(self, my_task): - my_task._sync_children(self.outputs, state=TaskState.MAYBE) - - def _on_ready_hook(self, my_task): - seen_events = my_task.internal_data.get('seen_events', []) - for child in my_task.children: - if child.task_spec.event_definition not in seen_events: - child.cancel() + def _run_hook(self, my_task): + result = super(_EndJoin, self)._run_hook(my_task) + my_task.workflow.data.update(my_task.data) + return result \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/data_spec.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/data_spec.py index 911ba97ed..eac7ac754 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/data_spec.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/data_spec.py @@ -1,3 +1,22 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + import logging from copy import deepcopy @@ -8,13 +27,13 @@ data_log = logging.getLogger('spiff.data') class BpmnDataSpecification: - def __init__(self, name, description=None): + def __init__(self, bpmn_id, bpmn_name=None): """ :param name: the variable (the BPMN ID) :param description: a human readable name (the BPMN name) """ - self.name = name - self.description = description or name + self.bpmn_id = bpmn_id + self.bpmn_name = bpmn_name # In the future, we can add schemas defining the objects here. def get(self, my_task, **kwargs): @@ -25,7 +44,7 @@ class BpmnDataSpecification: class BpmnDataStoreSpecification(BpmnDataSpecification): - def __init__(self, name, description, capacity=None, is_unlimited=None): + def __init__(self, bpmn_id, bpmn_name, capacity=None, is_unlimited=None): """ :param name: the name of the task data variable and data store key (the BPMN ID) :param description: the task description (the BPMN name) @@ -35,7 +54,7 @@ class BpmnDataStoreSpecification(BpmnDataSpecification): self.capacity = capacity or 0 self.is_unlimited = is_unlimited or True # In the future, we can add schemas defining the objects here. - super().__init__(name, description) + super().__init__(bpmn_id, bpmn_name) class BpmnIoSpecification: @@ -50,20 +69,20 @@ class DataObject(BpmnDataSpecification): def get(self, my_task): """Copy a value form the workflow data to the task data.""" - if self.name not in my_task.workflow.data: - message = f"The data object could not be read; '{self.name}' does not exist in the process." + if self.bpmn_id not in my_task.workflow.data: + message = f"The data object could not be read; '{self.bpmn_id}' does not exist in the process." raise WorkflowDataException(message, my_task, data_input=self) - my_task.data[self.name] = deepcopy(my_task.workflow.data[self.name]) - data_log.info(f'Read workflow variable {self.name}', extra=my_task.log_info()) + my_task.data[self.bpmn_id] = deepcopy(my_task.workflow.data[self.bpmn_id]) + data_log.info(f'Read workflow variable {self.bpmn_id}', extra=my_task.log_info()) def set(self, my_task): """Copy a value from the task data to the workflow data""" - if self.name not in my_task.data: - message = f"A data object could not be set; '{self.name}' not exist in the task." + if self.bpmn_id not in my_task.data: + message = f"A data object could not be set; '{self.bpmn_id}' not exist in the task." raise WorkflowDataException(message, my_task, data_output=self) - my_task.workflow.data[self.name] = deepcopy(my_task.data[self.name]) - del my_task.data[self.name] - data_log.info(f'Set workflow variable {self.name}', extra=my_task.log_info()) + my_task.workflow.data[self.bpmn_id] = deepcopy(my_task.data[self.bpmn_id]) + del my_task.data[self.bpmn_id] + data_log.info(f'Set workflow variable {self.bpmn_id}', extra=my_task.log_info()) class TaskDataReference(BpmnDataSpecification): diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/defaults.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/defaults.py new file mode 100644 index 000000000..dc843ca5c --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/defaults.py @@ -0,0 +1,122 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +from .mixins.bpmn_spec_mixin import BpmnSpecMixin + +from .mixins.manual_task import ManualTask as ManualTaskMixin +from .mixins.none_task import NoneTask as NoneTaskMixin +from .mixins.user_task import UserTask as UserTaskMixin + +from .mixins.exclusive_gateway import ExclusiveGateway as ExclusiveGatewayMixin +from .mixins.inclusive_gateway import InclusiveGateway as InclusiveGatewayMixin +from .mixins.parallel_gateway import ParallelGateway as ParallelGatewayMixin + +from .mixins.script_task import ScriptTask as ScriptTaskMixin +from .mixins.service_task import ServiceTask as ServiceTaskMixin +from .mixins.multiinstance_task import ( + StandardLoopTask as StandardLoopTaskMixin, + ParallelMultiInstanceTask as ParallelMultiInstanceTaskMixin, + SequentialMultiInstanceTask as SequentialMultiInstanceTaskMixin, +) + +from .mixins.subworkflow_task import ( + SubWorkflowTask as SubworkflowTaskMixin, + CallActivity as CallActivityMixin, + TransactionSubprocess as TransactionSubprocessMixin, +) + +from .mixins.events.start_event import StartEvent as StartEventMixin +from .mixins.events.end_event import EndEvent as EndEventMixin +from .mixins.events.intermediate_event import ( + IntermediateCatchEvent as IntermediateCatchEventMixin, + IntermediateThrowEvent as IntermediateThrowEventMixin, + SendTask as SendTaskMixin, + ReceiveTask as ReceiveTaskMixin, + EventBasedGateway as EventBasedGatewayMixin, + BoundaryEvent as BoundaryEventMixin, +) + +# In the future, we could have the parser take a bpmn task spec and construct these classes automatically +# However, I am NOT going to try to do that with the parser we have now + +class ManualTask(ManualTaskMixin, BpmnSpecMixin): + pass + +class NoneTask(NoneTaskMixin, BpmnSpecMixin): + pass + +class UserTask(UserTaskMixin, BpmnSpecMixin): + pass + +class ExclusiveGateway(ExclusiveGatewayMixin, BpmnSpecMixin): + pass + +class InclusiveGateway(InclusiveGatewayMixin, BpmnSpecMixin): + pass + +class ParallelGateway(ParallelGatewayMixin, BpmnSpecMixin): + pass + +class ScriptTask(ScriptTaskMixin, BpmnSpecMixin): + pass + +class ServiceTask(ServiceTaskMixin, BpmnSpecMixin): + pass + +class StandardLoopTask(StandardLoopTaskMixin, BpmnSpecMixin): + pass + +class ParallelMultiInstanceTask(ParallelMultiInstanceTaskMixin, BpmnSpecMixin): + pass + +class SequentialMultiInstanceTask(SequentialMultiInstanceTaskMixin, BpmnSpecMixin): + pass + +class SubWorkflowTask(SubworkflowTaskMixin, BpmnSpecMixin): + pass + +class CallActivity(CallActivityMixin, BpmnSpecMixin): + pass + +class TransactionSubprocess(TransactionSubprocessMixin, BpmnSpecMixin): + pass + +class StartEvent(StartEventMixin, BpmnSpecMixin): + pass + +class EndEvent(EndEventMixin, BpmnSpecMixin): + pass + +class IntermediateCatchEvent(IntermediateCatchEventMixin, BpmnSpecMixin): + pass + +class IntermediateThrowEvent(IntermediateThrowEventMixin, BpmnSpecMixin): + pass + +class SendTask(SendTaskMixin, BpmnSpecMixin): + pass + +class ReceiveTask(ReceiveTaskMixin, BpmnSpecMixin): + pass + +class EventBasedGateway(EventBasedGatewayMixin, BpmnSpecMixin): + pass + +class BoundaryEvent(BoundaryEventMixin, BpmnSpecMixin): + pass diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/events/event_definitions.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/event_definitions.py similarity index 90% rename from SpiffWorkflow/SpiffWorkflow/bpmn/specs/events/event_definitions.py rename to SpiffWorkflow/SpiffWorkflow/bpmn/specs/event_definitions.py index d2cbd4625..4bce1f1cb 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/events/event_definitions.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/event_definitions.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2012 Matthew Hampton +# Copyright (C) 2012 Matthew Hampton, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -24,7 +24,6 @@ from time import timezone as tzoffset, altzone as dstoffset, daylight as isdst from copy import deepcopy from SpiffWorkflow.exceptions import WorkflowException -from SpiffWorkflow.task import TaskState seconds_from_utc = dstoffset if isdst else tzoffset LOCALTZ = timezone(timedelta(seconds=-1 * seconds_from_utc)) @@ -42,11 +41,12 @@ class EventDefinition(object): and external flags. Default catch behavior is to set the event to fired """ - def __init__(self): + def __init__(self, description=None): # Ideally I'd mke these parameters, but I don't want to them to be parameters # for any subclasses (as they are based on event type, not user choice) and # I don't want to write a separate deserializer for every every type. self.internal, self.external = True, True + self.description = description @property def event_type(self): @@ -92,8 +92,8 @@ class NamedEventDefinition(EventDefinition): :param name: the name of this event """ - def __init__(self, name): - super(NamedEventDefinition, self).__init__() + def __init__(self, name, **kwargs): + super(NamedEventDefinition, self).__init__(**kwargs) self.name = name def reset(self, my_task): @@ -108,14 +108,10 @@ class CancelEventDefinition(EventDefinition): Cancel events are only handled by the outerworkflow, as they can only be used inside of transaction subprocesses. """ - def __init__(self): - super(CancelEventDefinition, self).__init__() + def __init__(self, **kwargs): + super(CancelEventDefinition, self).__init__(**kwargs) self.internal = False - @property - def event_type(self): - return 'Cancel' - class ErrorEventDefinition(NamedEventDefinition): """ @@ -123,15 +119,11 @@ class ErrorEventDefinition(NamedEventDefinition): matched by code rather than name. """ - def __init__(self, name, error_code=None): - super(ErrorEventDefinition, self).__init__(name) + def __init__(self, name, error_code=None, **kwargs): + super(ErrorEventDefinition, self).__init__(name,**kwargs) self.error_code = error_code self.internal = False - @property - def event_type(self): - return 'Error' - def __eq__(self, other): return self.__class__.__name__ == other.__class__.__name__ and self.error_code in [ None, other.error_code ] @@ -142,20 +134,16 @@ class EscalationEventDefinition(NamedEventDefinition): the spec says that the escalation code should be matched. """ - def __init__(self, name, escalation_code=None): + def __init__(self, name, escalation_code=None, **kwargs): """ Constructor. :param escalation_code: The escalation code this event should react to. If None then all escalations will activate this event. """ - super(EscalationEventDefinition, self).__init__(name) + super(EscalationEventDefinition, self).__init__(name, **kwargs) self.escalation_code = escalation_code - @property - def event_type(self): - return 'Escalation' - def __eq__(self, other): return self.__class__.__name__ == other.__class__.__name__ and self.escalation_code in [ None, other.escalation_code ] @@ -171,17 +159,13 @@ class CorrelationProperty: class MessageEventDefinition(NamedEventDefinition): """The default message event.""" - def __init__(self, name, correlation_properties=None): - super().__init__(name) + def __init__(self, name, correlation_properties=None, **kwargs): + super().__init__(name, **kwargs) self.correlation_properties = correlation_properties or [] self.payload = None self.internal = False - @property - def event_type(self): - return 'Message' - - def catch(self, my_task, event_definition = None): + def catch(self, my_task, event_definition=None): self.update_internal_data(my_task, event_definition) super(MessageEventDefinition, self).catch(my_task, event_definition) @@ -243,13 +227,10 @@ class NoneEventDefinition(EventDefinition): """ This class defines behavior for NoneEvents. We override throw to do nothing. """ - def __init__(self): + def __init__(self, **kwargs): + super().__init__(**kwargs) self.internal, self.external = False, False - @property - def event_type(self): - return 'Default' - def throw(self, my_task): """It's a 'none' event, so nothing to throw.""" pass @@ -261,26 +242,21 @@ class NoneEventDefinition(EventDefinition): class SignalEventDefinition(NamedEventDefinition): """The SignalEventDefinition is the implementation of event definition used for Signal Events.""" + def __init__(self, name, **kwargs): + super().__init__(name, **kwargs) - @property - def spec_type(self): - return 'Signal' class TerminateEventDefinition(EventDefinition): """The TerminateEventDefinition is the implementation of event definition used for Termination Events.""" - def __init__(self): - super(TerminateEventDefinition, self).__init__() + def __init__(self, **kwargs): + super(TerminateEventDefinition, self).__init__(**kwargs) self.external = False - @property - def event_type(self): - return 'Terminate' - class TimerEventDefinition(EventDefinition): - def __init__(self, name, expression): + def __init__(self, name, expression, **kwargs): """ Constructor. @@ -288,7 +264,7 @@ class TimerEventDefinition(EventDefinition): :param expression: An ISO 8601 datetime or interval expression. """ - super().__init__() + super().__init__(**kwargs) self.name = name self.expression = expression @@ -362,7 +338,7 @@ class TimerEventDefinition(EventDefinition): @staticmethod def parse_iso_week(expression): # https://en.wikipedia.org/wiki/ISO_8601#Week_dates - m = re.match('(\d{4})W(\d{2})(\d)(T.+)?', expression.upper().replace('-', '')) + m = re.match(r'(\d{4})W(\d{2})(\d)(T.+)?', expression.upper().replace('-', '')) year, month, day, ts = m.groups() ds = datetime.fromisocalendar(int(year), int(month), int(day)).strftime('%Y-%m-%d') return TimerEventDefinition.get_datetime(ds + (ts or '')) @@ -409,10 +385,6 @@ class TimerEventDefinition(EventDefinition): class TimeDateEventDefinition(TimerEventDefinition): """A Timer event represented by a specific date/time.""" - @property - def event_type(self): - return 'Time Date Timer' - def has_fired(self, my_task): event_value = my_task._get_internal_data('event_value') if event_value is None: @@ -429,10 +401,6 @@ class TimeDateEventDefinition(TimerEventDefinition): class DurationTimerEventDefinition(TimerEventDefinition): """A timer event represented by a duration""" - @property - def event_type(self): - return 'Duration Timer' - def has_fired(self, my_task): event_value = my_task._get_internal_data("event_value") if event_value is None: @@ -450,10 +418,6 @@ class DurationTimerEventDefinition(TimerEventDefinition): class CycleTimerEventDefinition(TimerEventDefinition): - @property - def event_type(self): - return 'Cycle Timer' - def cycle_complete(self, my_task): event_value = my_task._get_internal_data('event_value') @@ -489,15 +453,11 @@ class CycleTimerEventDefinition(TimerEventDefinition): class MultipleEventDefinition(EventDefinition): - def __init__(self, event_definitions=None, parallel=False): - super().__init__() + def __init__(self, event_definitions=None, parallel=False, **kwargs): + super().__init__(**kwargs) self.event_definitions = event_definitions or [] self.parallel = parallel - @property - def event_type(self): - return 'Multiple' - def has_fired(self, my_task): seen_events = my_task.internal_data.get('seen_events', []) diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/events/__init__.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/__init__.py similarity index 100% rename from SpiffWorkflow/SpiffWorkflow/bpmn/specs/events/__init__.py rename to SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/__init__.py diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/bpmn_spec_mixin.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/bpmn_spec_mixin.py new file mode 100644 index 000000000..8026186ff --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/bpmn_spec_mixin.py @@ -0,0 +1,28 @@ +# Copyright (C) 2012 Matthew Hampton, 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +from ..bpmn_task_spec import BpmnTaskSpec + + +class BpmnSpecMixin(BpmnTaskSpec): + + def __init__(self, wf_spec, bpmn_id, **kwargs): + super().__init__(wf_spec, bpmn_id, **kwargs) + self.bpmn_id = bpmn_id + self.bpmn_name = kwargs.get('bpmn_name') diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/events/__init__.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/events/__init__.py new file mode 100644 index 000000000..b93acc8d9 --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/events/__init__.py @@ -0,0 +1,18 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/events/EndEvent.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/events/end_event.py similarity index 77% rename from SpiffWorkflow/SpiffWorkflow/bpmn/specs/events/EndEvent.py rename to SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/events/end_event.py index 8f5bf665c..e19959e51 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/events/EndEvent.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/events/end_event.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2012 Matthew Hampton +# Copyright (C) 2012 Matthew Hampton, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -17,9 +17,9 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA +from SpiffWorkflow.task import TaskState from .event_types import ThrowingEvent -from .event_definitions import TerminateEventDefinition, CancelEventDefinition -from ....task import TaskState +from ...event_definitions import TerminateEventDefinition, CancelEventDefinition class EndEvent(ThrowingEvent): @@ -41,14 +41,6 @@ class EndEvent(ThrowingEvent): Gateways, one of the associated Events has been triggered. * There is no token remaining within the Process instance. """ - - def __init__(self, wf_spec, name, event_definition, **kwargs): - super(EndEvent, self).__init__(wf_spec, name, event_definition, **kwargs) - - @property - def spec_type(self): - return 'End Event' - def _on_complete_hook(self, my_task): super(EndEvent, self)._on_complete_hook(my_task) diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/events/event_types.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/events/event_types.py similarity index 71% rename from SpiffWorkflow/SpiffWorkflow/bpmn/specs/events/event_types.py rename to SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/events/event_types.py index 996f1d9a8..34ef42b1a 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/events/event_types.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/events/event_types.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2012 Matthew Hampton +# Copyright (C) 2012 Matthew Hampton, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,22 +16,22 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA +import time +from SpiffWorkflow.task import TaskState +from SpiffWorkflow.specs.base import TaskSpec +from ...event_definitions import MessageEventDefinition, NoneEventDefinition, CycleTimerEventDefinition -from .event_definitions import MessageEventDefinition, NoneEventDefinition, CycleTimerEventDefinition -from ..BpmnSpecMixin import BpmnSpecMixin -from ....specs.Simple import Simple -from ....task import TaskState -class CatchingEvent(Simple, BpmnSpecMixin): +class CatchingEvent(TaskSpec): """Base Task Spec for Catching Event nodes.""" - def __init__(self, wf_spec, name, event_definition, **kwargs): + def __init__(self, wf_spec, bpmn_id, event_definition, **kwargs): """ Constructor. :param event_definition: the EventDefinition that we must wait for. """ - super(CatchingEvent, self).__init__(wf_spec, name, **kwargs) + super(CatchingEvent, self).__init__(wf_spec, bpmn_id, **kwargs) self.event_definition = event_definition def catches(self, my_task, event_definition, correlations=None): @@ -46,6 +46,7 @@ class CatchingEvent(Simple, BpmnSpecMixin): definition, at which point we can update our task's state. """ self.event_definition.catch(my_task, event_definition) + my_task.last_update_time = time.time() my_task._set_state(TaskState.WAITING) def _update_hook(self, my_task): @@ -74,26 +75,17 @@ class CatchingEvent(Simple, BpmnSpecMixin): self.event_definition.reset(my_task) return super(CatchingEvent, self)._run_hook(my_task) - # This fixes the problem of boundary events remaining cancelled if the task is reused. - # It pains me to add these methods, but unless we can get rid of the loop reset task we're stuck - def task_should_set_children_future(self, my_task): - return True - - def task_will_set_children_future(self, my_task): - my_task.internal_data = {} - - -class ThrowingEvent(Simple, BpmnSpecMixin): +class ThrowingEvent(TaskSpec): """Base Task Spec for Throwing Event nodes.""" - def __init__(self, wf_spec, name, event_definition, **kwargs): + def __init__(self, wf_spec, bpmn_id, event_definition, **kwargs): """ Constructor. :param event_definition: the EventDefinition to be thrown. """ - super(ThrowingEvent, self).__init__(wf_spec, name, **kwargs) + super(ThrowingEvent, self).__init__(wf_spec, bpmn_id, **kwargs) self.event_definition = event_definition def _run_hook(self, my_task): diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/events/intermediate_event.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/events/intermediate_event.py new file mode 100644 index 000000000..c2382b6a0 --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/events/intermediate_event.py @@ -0,0 +1,63 @@ +# Copyright (C) 2012 Matthew Hampton, 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +from SpiffWorkflow.task import TaskState +from .event_types import ThrowingEvent, CatchingEvent + + +class SendTask(ThrowingEvent): + pass + +class ReceiveTask(CatchingEvent): + pass + +class IntermediateCatchEvent(CatchingEvent): + pass + +class IntermediateThrowEvent(ThrowingEvent): + pass + + +class BoundaryEvent(CatchingEvent): + """Task Spec for a bpmn:boundaryEvent node.""" + + def __init__(self, wf_spec, bpmn_id, event_definition, cancel_activity, **kwargs): + """ + Constructor. + + :param cancel_activity: True if this is a Cancelling boundary event. + """ + super(BoundaryEvent, self).__init__(wf_spec, bpmn_id, event_definition, **kwargs) + self.cancel_activity = cancel_activity + + def catches(self, my_task, event_definition, correlations=None): + # Boundary events should only be caught while waiting + return super(BoundaryEvent, self).catches(my_task, event_definition, correlations) and my_task.state == TaskState.WAITING + + +class EventBasedGateway(CatchingEvent): + + def _predict_hook(self, my_task): + my_task._sync_children(self.outputs, state=TaskState.MAYBE) + + def _on_ready_hook(self, my_task): + seen_events = my_task.internal_data.get('seen_events', []) + for child in my_task.children: + if child.task_spec.event_definition not in seen_events: + child.cancel() diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/events/StartEvent.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/events/start_event.py similarity index 62% rename from SpiffWorkflow/SpiffWorkflow/bpmn/specs/events/StartEvent.py rename to SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/events/start_event.py index e3b1a7cfb..b06b5bc9b 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/events/StartEvent.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/events/start_event.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2012 Matthew Hampton +# Copyright (C) 2012 Matthew Hampton, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -17,27 +17,16 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA +from SpiffWorkflow.task import TaskState from .event_types import CatchingEvent -from ....task import TaskState class StartEvent(CatchingEvent): """Task Spec for a bpmn:startEvent node with an optional event definition.""" - def __init__(self, wf_spec, name, event_definition, **kwargs): - super(StartEvent, self).__init__(wf_spec, name, event_definition, **kwargs) - - @property - def spec_type(self): - return f'{self.event_definition.event_type} Start Event' - def catch(self, my_task, event_definition): - # We might need to revisit a start event after it completes or # if it got cancelled so we'll still catch messages even if we're finished if my_task.state == TaskState.COMPLETED or my_task.state == TaskState.CANCELLED: - my_task.set_children_future() - my_task._set_state(TaskState.WAITING) - + my_task.workflow.reset_from_task_id(my_task.id) super(StartEvent, self).catch(my_task, event_definition) - diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/ExclusiveGateway.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/exclusive_gateway.py similarity index 61% rename from SpiffWorkflow/SpiffWorkflow/bpmn/specs/ExclusiveGateway.py rename to SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/exclusive_gateway.py index 46ceb9090..f5cfdaef8 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/ExclusiveGateway.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/exclusive_gateway.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2012 Matthew Hampton # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -17,12 +17,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -from .BpmnSpecMixin import BpmnSpecMixin -from ...specs.ExclusiveChoice import ExclusiveChoice -from ...specs.MultiChoice import MultiChoice +from SpiffWorkflow.specs.ExclusiveChoice import ExclusiveChoice +from SpiffWorkflow.specs.MultiChoice import MultiChoice -class ExclusiveGateway(ExclusiveChoice, BpmnSpecMixin): +class ExclusiveGateway(ExclusiveChoice): """ Task Spec for a bpmn:exclusiveGateway node. """ @@ -31,6 +30,3 @@ class ExclusiveGateway(ExclusiveChoice, BpmnSpecMixin): # Bypass the check for no default output -- this is not required in BPMN MultiChoice.test(self) - @property - def spec_type(self): - return 'Exclusive Gateway' diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/InclusiveGateway.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/inclusive_gateway.py similarity index 88% rename from SpiffWorkflow/SpiffWorkflow/bpmn/specs/InclusiveGateway.py rename to SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/inclusive_gateway.py index 157f0a586..84d1fdc2b 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/InclusiveGateway.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/inclusive_gateway.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2012 Matthew Hampton +# Copyright (C) 2012 Matthew Hampton, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -17,10 +17,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -from SpiffWorkflow.exceptions import WorkflowTaskException -from ...task import TaskState -from .UnstructuredJoin import UnstructuredJoin -from ...specs.MultiChoice import MultiChoice +from SpiffWorkflow.bpmn.exceptions import WorkflowTaskException +from SpiffWorkflow.task import TaskState +from SpiffWorkflow.specs.MultiChoice import MultiChoice +from .unstructured_join import UnstructuredJoin + class InclusiveGateway(MultiChoice, UnstructuredJoin): @@ -113,10 +114,6 @@ class InclusiveGateway(MultiChoice, UnstructuredJoin): def _run_hook(self, my_task): outputs = self._get_matching_outputs(my_task) if len(outputs) == 0: - raise WorkflowTaskException(f'No conditions satisfied on gateway', task=my_task) + raise WorkflowTaskException('No conditions satisfied on gateway', task=my_task) my_task._sync_children(outputs, TaskState.FUTURE) return True - - @property - def spec_type(self): - return 'Inclusive Gateway' \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/ManualTask.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/manual_task.py similarity index 54% rename from SpiffWorkflow/SpiffWorkflow/bpmn/specs/ManualTask.py rename to SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/manual_task.py index 6e4c7ecaf..73fbc07f4 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/ManualTask.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/manual_task.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2012 Matthew Hampton # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,16 +16,13 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -from ...bpmn.specs.BpmnSpecMixin import BpmnSpecMixin -from ...specs.Simple import Simple +from SpiffWorkflow.specs.base import TaskSpec -class ManualTask(Simple, BpmnSpecMixin): +class ManualTask(TaskSpec): + """Task Spec for a bpmn:manualTask node.""" - def is_engine_task(self): - return False - - @property - def spec_type(self): - return 'Manual Task' + def __init__(self, wf_spec, bpmn_id, **kwargs): + super().__init__(wf_spec, bpmn_id, **kwargs) + self.manual = True diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/MultiInstanceTask.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/multiinstance_task.py similarity index 86% rename from SpiffWorkflow/SpiffWorkflow/bpmn/specs/MultiInstanceTask.py rename to SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/multiinstance_task.py index 8e6d94416..d10ec82d9 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/MultiInstanceTask.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/multiinstance_task.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2020 Sartography +# Copyright (C) 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -20,17 +20,17 @@ from copy import deepcopy from collections.abc import Iterable, Sequence, Mapping, MutableSequence, MutableMapping -from ...task import TaskState -from ...util.deep_merge import DeepMerge -from ..exceptions import WorkflowDataException -from .BpmnSpecMixin import BpmnSpecMixin +from SpiffWorkflow.task import TaskState +from SpiffWorkflow.specs.base import TaskSpec +from SpiffWorkflow.util.deep_merge import DeepMerge +from SpiffWorkflow.bpmn.exceptions import WorkflowDataException -class LoopTask(BpmnSpecMixin): +class LoopTask(TaskSpec): def process_children(self, my_task): """ - Handle any newly completed children and update merged tasks. + Handle any newly completed children and update merged tasks. Returns a boolean indicating whether there is a child currently running """ merged = my_task.internal_data.get('merged') or [] @@ -42,7 +42,7 @@ class LoopTask(BpmnSpecMixin): elif not child._has_state(TaskState.FINISHED_MASK): child_running = True my_task.internal_data['merged'] = merged - return child_running + return child_running def child_completed_action(self, my_task, child): raise NotImplementedError @@ -50,8 +50,8 @@ class LoopTask(BpmnSpecMixin): class StandardLoopTask(LoopTask): - def __init__(self, wf_spec, name, task_spec, maximum, condition, test_before, **kwargs): - super().__init__(wf_spec, name, **kwargs) + def __init__(self, wf_spec, bpmn_id, task_spec, maximum, condition, test_before, **kwargs): + super().__init__(wf_spec, bpmn_id, **kwargs) self.task_spec = task_spec self.maximum = maximum self.condition = condition @@ -59,7 +59,9 @@ class StandardLoopTask(LoopTask): def _update_hook(self, my_task): - super()._update_hook(my_task) + if my_task.state != TaskState.WAITING: + super()._update_hook(my_task) + child_running = self.process_children(my_task) if child_running: # We're in the middle of an iteration; we're not done and we can't create a new task @@ -72,7 +74,7 @@ class StandardLoopTask(LoopTask): if my_task.state != TaskState.WAITING: my_task._set_state(TaskState.WAITING) task_spec = my_task.workflow.spec.task_specs[self.task_spec] - child = my_task._add_child(task_spec, TaskState.READY) + child = my_task._add_child(task_spec, TaskState.WAITING) child.data = deepcopy(my_task.data) def child_completed_action(self, my_task, child): @@ -92,11 +94,11 @@ class StandardLoopTask(LoopTask): class MultiInstanceTask(LoopTask): - def __init__(self, wf_spec, name, task_spec, cardinality=None, data_input=None, + def __init__(self, wf_spec, bpmn_id, task_spec, cardinality=None, data_input=None, data_output=None, input_item=None, output_item=None, condition=None, **kwargs): - super().__init__(wf_spec, name, **kwargs) + super().__init__(wf_spec, bpmn_id, **kwargs) self.task_spec = task_spec self.cardinality = cardinality self.data_input = data_input @@ -109,13 +111,13 @@ class MultiInstanceTask(LoopTask): """This merges child data into this task's data.""" if self.data_output is not None and self.output_item is not None: - if self.output_item.name not in child.data: + if self.output_item.bpmn_id not in child.data: self.raise_data_exception("Expected an output item", child) - item = child.data[self.output_item.name] + item = child.data[self.output_item.bpmn_id] key_or_index = child.internal_data.get('key_or_index') - data_output = my_task.data[self.data_output.name] - data_input = my_task.data[self.data_input.name] if self.data_input is not None else None - if isinstance(data_output, Mapping) or data_input is data_output: + data_output = my_task.data[self.data_output.bpmn_id] + data_input = my_task.data[self.data_input.bpmn_id] if self.data_input is not None else None + if key_or_index is not None and (isinstance(data_output, Mapping) or data_input is data_output): data_output[key_or_index] = item else: data_output.append(item) @@ -128,7 +130,7 @@ class MultiInstanceTask(LoopTask): child = my_task._add_child(task_spec, TaskState.WAITING) child.data = deepcopy(my_task.data) if self.input_item is not None: - child.data[self.input_item.name] = deepcopy(item) + child.data[self.input_item.bpmn_id] = deepcopy(item) if key_or_index is not None: child.internal_data['key_or_index'] = key_or_index child.task_spec._update(child) @@ -142,7 +144,7 @@ class MultiInstanceTask(LoopTask): def init_data_output_with_input_data(self, my_task, input_data): - name = self.data_output.name + name = self.data_output.bpmn_id if name not in my_task.data: if isinstance(input_data, (MutableMapping, MutableSequence)): # We can use the same class if it implements __setitem__ @@ -154,7 +156,7 @@ class MultiInstanceTask(LoopTask): # For all other types, we'll append to a list my_task.data[name] = list() else: - output_data = my_task.data[self.data_output.name] + output_data = my_task.data[self.data_output.bpmn_id] if not isinstance(output_data, (MutableSequence, MutableMapping)): self.raise_data_exception("Only a mutable map (dict) or sequence (list) can be used for output", my_task) if input_data is not output_data and not isinstance(output_data, Mapping) and len(output_data) > 0: @@ -163,7 +165,7 @@ class MultiInstanceTask(LoopTask): def init_data_output_with_cardinality(self, my_task): - name = self.data_output.name + name = self.data_output.bpmn_id if name not in my_task.data: my_task.data[name] = list() elif not isinstance(my_task.data[name], MutableMapping) and len(my_task.data[name]) > 0: @@ -207,7 +209,7 @@ class SequentialMultiInstanceTask(MultiInstanceTask): def get_next_input_item(self, my_task): - input_data = my_task.data[self.data_input.name] + input_data = my_task.data[self.data_input.bpmn_id] remaining = my_task.internal_data.get('remaining') if remaining is None: @@ -229,9 +231,9 @@ class SequentialMultiInstanceTask(MultiInstanceTask): def init_remaining_items(self, my_task): - if self.data_input.name not in my_task.data: + if self.data_input.bpmn_id not in my_task.data: self.raise_data_exception("Missing data input for multiinstance task", my_task) - input_data = my_task.data[self.data_input.name] + input_data = my_task.data[self.data_input.bpmn_id] # This is internal bookkeeping, so we know where we are; we get the actual items when we create the task if isinstance(input_data, Sequence): @@ -270,7 +272,7 @@ class SequentialMultiInstanceTask(MultiInstanceTask): class ParallelMultiInstanceTask(MultiInstanceTask): - + def _update_hook(self, my_task): if my_task.state != TaskState.WAITING: @@ -287,7 +289,7 @@ class ParallelMultiInstanceTask(MultiInstanceTask): def create_children(self, my_task): - data_input = my_task.data[self.data_input.name] if self.data_input is not None else None + data_input = my_task.data[self.data_input.bpmn_id] if self.data_input is not None else None if data_input is not None: # We have to preserve the key or index for maps/sequences, in case we're updating in place, or the output is a mapping if isinstance(data_input, Mapping): @@ -306,7 +308,7 @@ class ParallelMultiInstanceTask(MultiInstanceTask): if self.data_output is not None: if self.data_input is not None: - self.init_data_output_with_input_data(my_task, my_task.data[self.data_input.name]) + self.init_data_output_with_input_data(my_task, my_task.data[self.data_input.bpmn_id]) else: self.init_data_output_with_cardinality(my_task) diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/NoneTask.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/none_task.py similarity index 50% rename from SpiffWorkflow/SpiffWorkflow/bpmn/specs/NoneTask.py rename to SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/none_task.py index 7d14a9b98..b4cabb924 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/NoneTask.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/none_task.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2012 Matthew Hampton +# Copyright (C) 2012 Matthew Hampton, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,16 +16,13 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -from ...specs.Simple import Simple -from ...bpmn.specs.BpmnSpecMixin import BpmnSpecMixin +from SpiffWorkflow.specs.base import TaskSpec -class NoneTask(Simple, BpmnSpecMixin): +class NoneTask(TaskSpec): + """Task Spec for a bpmn:task node.""" - def is_engine_task(self): - return False - - @property - def spec_type(self): - return 'Task' + def __init__(self, wf_spec, bpmn_id, **kwargs): + super().__init__(wf_spec, bpmn_id, **kwargs) + self.manual = True diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/ParallelGateway.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/parallel_gateway.py similarity index 80% rename from SpiffWorkflow/SpiffWorkflow/bpmn/specs/ParallelGateway.py rename to SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/parallel_gateway.py index bf2f4c880..24a561a36 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/ParallelGateway.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/parallel_gateway.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2012 Matthew Hampton +# Copyright (C) 2012 Matthew Hampton, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,7 +16,8 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -from .UnstructuredJoin import UnstructuredJoin + +from .unstructured_join import UnstructuredJoin class ParallelGateway(UnstructuredJoin): @@ -43,7 +44,3 @@ class ParallelGateway(UnstructuredJoin): def _check_threshold_unstructured(self, my_task, force=False): completed_inputs, waiting_tasks = self._get_inputs_with_tokens(my_task) return force or len(completed_inputs) >= len(self.inputs), waiting_tasks - - @property - def spec_type(self): - return 'Parallel Gateway' diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/ScriptTask.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/script_task.py similarity index 65% rename from SpiffWorkflow/SpiffWorkflow/bpmn/specs/ScriptTask.py rename to SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/script_task.py index e00f70391..eea6506dd 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/ScriptTask.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/script_task.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2012 Matthew Hampton +# Copyright (C) 2012 Matthew Hampton, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -17,11 +17,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -from .BpmnSpecMixin import BpmnSpecMixin -from ...specs.Simple import Simple +from SpiffWorkflow.specs.base import TaskSpec -class ScriptEngineTask(Simple, BpmnSpecMixin): +class ScriptEngineTask(TaskSpec): """Task Spec for a bpmn:scriptTask node""" def _execute(self, task): @@ -34,18 +33,14 @@ class ScriptEngineTask(Simple, BpmnSpecMixin): class ScriptTask(ScriptEngineTask): - def __init__(self, wf_spec, name, script, **kwargs): + def __init__(self, wf_spec, bpmn_id, script, **kwargs): """ Constructor. :param script: the script that must be executed by the script engine. """ - super(ScriptTask, self).__init__(wf_spec, name, **kwargs) + super(ScriptTask, self).__init__(wf_spec, bpmn_id, **kwargs) self.script = script - @property - def spec_type(self): - return 'Script Task' - def _execute(self, task): return task.workflow.script_engine.execute(task, self.script) diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/UserTask.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/service_task.py similarity index 51% rename from SpiffWorkflow/SpiffWorkflow/bpmn/specs/UserTask.py rename to SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/service_task.py index 95a1e4f10..06428199b 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/UserTask.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/service_task.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2012 Matthew Hampton +# Copyright (C) 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -17,19 +17,14 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -from .BpmnSpecMixin import BpmnSpecMixin -from ...specs.Simple import Simple +from .script_task import ScriptEngineTask -class UserTask(Simple, BpmnSpecMixin): +class ServiceTask(ScriptEngineTask): """ - Task Spec for a bpmn:userTask node. + Task Spec for a bpmn:serviceTask node. """ - def is_engine_task(self): - return False - - @property - def spec_type(self): - return 'User Task' \ No newline at end of file + def __init__(self, wf_spec, bpmn_id, **kwargs): + super(ServiceTask, self).__init__(wf_spec, bpmn_id, **kwargs) diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/SubWorkflowTask.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/subworkflow_task.py similarity index 55% rename from SpiffWorkflow/SpiffWorkflow/bpmn/specs/SubWorkflowTask.py rename to SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/subworkflow_task.py index 9ba453931..a42b4c936 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/SubWorkflowTask.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/subworkflow_task.py @@ -1,30 +1,47 @@ -# -*- coding: utf-8 -*- +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from copy import deepcopy from SpiffWorkflow.task import TaskState -from .BpmnSpecMixin import BpmnSpecMixin -from ..exceptions import WorkflowDataException +from SpiffWorkflow.specs.base import TaskSpec +from SpiffWorkflow.bpmn.specs.control import _BoundaryEventParent +from SpiffWorkflow.bpmn.specs.mixins.events.intermediate_event import BoundaryEvent + +from SpiffWorkflow.bpmn.exceptions import WorkflowDataException -class SubWorkflowTask(BpmnSpecMixin): +class SubWorkflowTask(TaskSpec): """ Task Spec for a bpmn node containing a subworkflow. """ - def __init__(self, wf_spec, name, subworkflow_spec, transaction=False, **kwargs): + def __init__(self, wf_spec, bpmn_id, subworkflow_spec, transaction=False, **kwargs): """ Constructor. :param bpmn_wf_spec: the BpmnProcessSpec for the sub process. :param bpmn_wf_class: the BpmnWorkflow class to instantiate """ - super(SubWorkflowTask, self).__init__(wf_spec, name, **kwargs) + super(SubWorkflowTask, self).__init__(wf_spec, bpmn_id, **kwargs) self.spec = subworkflow_spec self.transaction = transaction - @property - def spec_type(self): - return 'Subprocess' - def _on_subworkflow_completed(self, subworkflow, my_task): self.update_data(my_task, subworkflow) @@ -46,7 +63,7 @@ class SubWorkflowTask(BpmnSpecMixin): def copy_data(self, my_task, subworkflow): # There is only one copy of any given data object, so it should be updated immediately - # Doing this is actually a little problematic, because it gives parent processes access to + # Doing this is actually a little problematic, because it gives parent processes access to # data objects defined in subprocesses. # But our data management is already hopelessly messed up and in dire needs of reconsideration if len(subworkflow.spec.data_objects) > 0: @@ -68,14 +85,11 @@ class SubWorkflowTask(BpmnSpecMixin): child.task_spec._update(child) my_task._set_state(TaskState.WAITING) - def task_will_set_children_future(self, my_task): - my_task.workflow.delete_subprocess(my_task) - class CallActivity(SubWorkflowTask): - def __init__(self, wf_spec, name, subworkflow_spec, **kwargs): - super(CallActivity, self).__init__(wf_spec, name, subworkflow_spec, False, **kwargs) + def __init__(self, wf_spec, bpmn_id, subworkflow_spec, **kwargs): + super(CallActivity, self).__init__(wf_spec, bpmn_id, subworkflow_spec, False, **kwargs) def copy_data(self, my_task, subworkflow): @@ -86,13 +100,13 @@ class CallActivity(SubWorkflowTask): else: # Otherwise copy only task data with the specified names for var in subworkflow.spec.io_specification.data_inputs: - if var.name not in my_task.data: + if var.bpmn_id not in my_task.data: raise WorkflowDataException( "You are missing a required Data Input for a call activity.", task=my_task, data_input=var, ) - start[0].data[var.name] = my_task.data[var.name] + start[0].data[var.bpmn_id] = my_task.data[var.bpmn_id] def update_data(self, my_task, subworkflow): @@ -103,25 +117,36 @@ class CallActivity(SubWorkflowTask): end = subworkflow.get_tasks_from_spec_name('End', workflow=subworkflow) # Otherwise only copy data with the specified names for var in subworkflow.spec.io_specification.data_outputs: - if var.name not in end[0].data: + if var.bpmn_id not in end[0].data: raise WorkflowDataException( - f"The Data Output was not available in the subprocess output.", + "The Data Output was not available in the subprocess output.", task=my_task, data_output=var, ) - my_task.data[var.name] = end[0].data[var.name] - - @property - def spec_type(self): - return 'Call Activity' + my_task.data[var.bpmn_id] = end[0].data[var.bpmn_id] class TransactionSubprocess(SubWorkflowTask): - def __init__(self, wf_spec, name, subworkflow_spec, **kwargs): - super(TransactionSubprocess, self).__init__(wf_spec, name, subworkflow_spec, True, **kwargs) - - @property - def spec_type(self): - return 'Transactional Subprocess' + def __init__(self, wf_spec, bpmn_id, subworkflow_spec, **kwargs): + super(TransactionSubprocess, self).__init__(wf_spec, bpmn_id, subworkflow_spec, True, **kwargs) + def _on_complete_hook(self, my_task): + # It is possible that a transaction could end by throwing an event caught by a boundary event attached to it + # In that case both the subprocess and the boundary event become ready and whichever one gets executed + # first will cancel the other. + # So here I'm checking whether this has happened and cancelling this task in that case. + # I really hate this fix, so I'm only putting it in transactions because that's where I'm having the problem, + # but it's likely to be a general issue that we miraculously haven't run up against. + # We desperately need to get rid of this BonudaryEventParent BS. + parent = my_task.parent + if isinstance(parent.task_spec, _BoundaryEventParent) and len( + [t for t in parent.children if + isinstance(t.task_spec, BoundaryEvent) and + t.task_spec.cancel_activity and + t.state==TaskState.READY + ]): + my_task._drop_children() + my_task._set_state(TaskState.CANCELLED) + else: + super()._on_complete_hook(my_task) diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/UnstructuredJoin.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/unstructured_join.py similarity index 79% rename from SpiffWorkflow/SpiffWorkflow/bpmn/specs/UnstructuredJoin.py rename to SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/unstructured_join.py index 6b36a4040..7571d02c0 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/UnstructuredJoin.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/unstructured_join.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2012 Matthew Hampton +# Copyright (C) 2012 Matthew Hampton, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -17,12 +17,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -from ...task import TaskState -from .BpmnSpecMixin import BpmnSpecMixin -from ...specs.Join import Join +from SpiffWorkflow.task import TaskState +from SpiffWorkflow.specs.Join import Join -class UnstructuredJoin(Join, BpmnSpecMixin): +class UnstructuredJoin(Join): """ A helper subclass of Join that makes it work in a slightly friendlier way for the BPMN style threading @@ -83,13 +82,3 @@ class UnstructuredJoin(Join, BpmnSpecMixin): task._drop_children() else: task.data.update(collected_data) - - def task_should_set_children_future(self, my_task): - return True - - def task_will_set_children_future(self, my_task): - # go find all of the gateways with the same name as this one, - # drop children and set state to WAITING - for t in list(my_task.workflow.task_tree): - if t.task_spec.name == self.name and t.state == TaskState.COMPLETED: - t._set_state(TaskState.WAITING) diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/user_task.py b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/user_task.py new file mode 100644 index 000000000..47d72570f --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/user_task.py @@ -0,0 +1,27 @@ +# Copyright (C) 2012 Matthew Hampton, 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +from SpiffWorkflow.specs.base import TaskSpec + +class UserTask(TaskSpec): + """Task Spec for a bpmn:userTask node.""" + + def __init__(self, wf_spec, bpmn_id, **kwargs): + super().__init__(wf_spec, bpmn_id, **kwargs) + self.manual = True diff --git a/SpiffWorkflow/SpiffWorkflow/bpmn/workflow.py b/SpiffWorkflow/SpiffWorkflow/bpmn/workflow.py index fec316f9e..f658a8dfb 100644 --- a/SpiffWorkflow/SpiffWorkflow/bpmn/workflow.py +++ b/SpiffWorkflow/SpiffWorkflow/bpmn/workflow.py @@ -1,12 +1,13 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2012 Matthew Hampton +# Copyright (C) 2012 Matthew Hampton, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -15,21 +16,26 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + import copy -from SpiffWorkflow.bpmn.specs.events.event_definitions import ( +from SpiffWorkflow.task import TaskState, Task +from SpiffWorkflow.workflow import Workflow +from SpiffWorkflow.exceptions import WorkflowException, TaskNotFoundException +from SpiffWorkflow.bpmn.exceptions import WorkflowTaskException + +from SpiffWorkflow.bpmn.specs.mixins.events.event_types import CatchingEvent +from SpiffWorkflow.bpmn.specs.mixins.events.start_event import StartEvent +from SpiffWorkflow.bpmn.specs.mixins.subworkflow_task import CallActivity +from SpiffWorkflow.bpmn.specs.event_definitions import ( MessageEventDefinition, MultipleEventDefinition, NamedEventDefinition, TimerEventDefinition, ) + +from SpiffWorkflow.bpmn.specs.control import _BoundaryEventParent from .PythonScriptEngine import PythonScriptEngine -from .specs.events.event_types import CatchingEvent -from .specs.events.StartEvent import StartEvent -from .specs.SubWorkflowTask import CallActivity -from ..task import TaskState, Task -from ..workflow import Workflow -from ..exceptions import TaskNotFoundException, WorkflowException, WorkflowTaskException class BpmnMessage: @@ -86,7 +92,8 @@ class BpmnWorkflow(Workflow): def delete_subprocess(self, my_task): workflow = self._get_outermost_workflow(my_task) - del workflow.subprocesses[my_task.id] + if my_task.id in workflow.subprocesses: + del workflow.subprocesses[my_task.id] def get_subprocess(self, my_task): workflow = self._get_outermost_workflow(my_task) @@ -94,7 +101,17 @@ class BpmnWorkflow(Workflow): def connect_subprocess(self, spec_name, name): # This creates a new task associated with a process when an event that kicks of a process is received - new = CallActivity(self.spec, name, spec_name) + # I need to know what class is being used to create new processes in this case, and this seems slightly + # less bad than adding yet another argument. Still sucks though. + # TODO: Make collaborations a class rather than trying to shoehorn them into a process. + for spec in self.spec.task_specs.values(): + if isinstance(spec, CallActivity): + spec_class = spec.__class__ + break + else: + # Default to the mixin class, which will probably fail in many cases. + spec_class = CallActivity + new = spec_class(self.spec, name, spec_name) self.spec.start.connect(new) task = Task(self, new) start = self.get_tasks_from_spec_name('Start', workflow=self)[0] @@ -135,7 +152,7 @@ class BpmnWorkflow(Workflow): :param event_definition: the thrown event """ # Start a subprocess for known specs with start events that catch this - # This is total hypocritical of me given how I've argued that specs should + # This is totally hypocritical of me given how I've argued that specs should # be immutable, but I see no other way of doing this. for name, spec in self.subprocess_specs.items(): for task_spec in list(spec.task_specs.values()): @@ -183,8 +200,8 @@ class BpmnWorkflow(Workflow): conversation = task.task_spec.event_definition.conversation() if not conversation: raise WorkflowTaskException( - f"The waiting task and message payload can not be matched to any correlation key (conversation topic). " - f"And is therefor unable to respond to the given message.", task) + "The waiting task and message payload can not be matched to any correlation key (conversation topic). " + "And is therefor unable to respond to the given message.", task) updated_props = self._correlate(conversation, payload, task) task.task_spec.catch(task, event_definition) self.refresh_waiting_tasks() @@ -229,7 +246,7 @@ class BpmnWorkflow(Workflow): elif isinstance(event_definition, MessageEventDefinition): value = event_definition.correlation_properties events.append({ - 'event_type': event_definition.event_type, + 'event_type': event_definition.__class__.__name__, 'name': event_definition.name if isinstance(event_definition, NamedEventDefinition) else None, 'value': value }) @@ -246,7 +263,7 @@ class BpmnWorkflow(Workflow): :param will_complete_task: Callback that will be called prior to completing a task :param did_complete_task: Callback that will be called after completing a task """ - engine_steps = list([t for t in self.get_tasks(TaskState.READY) if self._is_engine_task(t.task_spec)]) + engine_steps = list([t for t in self.get_tasks(TaskState.READY) if not t.task_spec.manual]) while engine_steps: for task in engine_steps: if will_complete_task is not None: @@ -256,7 +273,7 @@ class BpmnWorkflow(Workflow): did_complete_task(task) if task.task_spec.name == exit_at: return task - engine_steps = list([t for t in self.get_tasks(TaskState.READY) if self._is_engine_task(t.task_spec)]) + engine_steps = list([t for t in self.get_tasks(TaskState.READY) if not t.task_spec.manual]) def refresh_waiting_tasks(self, will_refresh_task=None, @@ -292,10 +309,10 @@ class BpmnWorkflow(Workflow): # almost surely be in a different state than the tasks we want for task in Workflow.get_tasks_iterator(wf): subprocess = top.subprocesses.get(task.id) - if subprocess is not None: - tasks.extend(subprocess.get_tasks(state, subprocess)) if task._has_state(state): tasks.append(task) + if subprocess is not None: + tasks.extend(subprocess.get_tasks(state, subprocess)) return tasks def get_task_from_id(self, task_id, workflow=None): @@ -307,12 +324,10 @@ class BpmnWorkflow(Workflow): def get_ready_user_tasks(self, lane=None, workflow=None): """Returns a list of User Tasks that are READY for user action""" if lane is not None: - return [t for t in self.get_tasks(TaskState.READY, workflow) - if (not self._is_engine_task(t.task_spec)) - and (t.task_spec.lane == lane)] + return [t for t in self.get_tasks(TaskState.READY, workflow) + if t.task_spec.manual and t.task_spec.lane == lane] else: - return [t for t in self.get_tasks(TaskState.READY, workflow) - if not self._is_engine_task(t.task_spec)] + return [t for t in self.get_tasks(TaskState.READY, workflow) if t.task_spec.manual] def get_waiting_tasks(self, workflow=None): """Returns a list of all WAITING tasks""" @@ -321,5 +336,58 @@ class BpmnWorkflow(Workflow): def get_catching_tasks(self, workflow=None): return [task for task in self.get_tasks(workflow=workflow) if isinstance(task.task_spec, CatchingEvent)] - def _is_engine_task(self, task_spec): - return (not hasattr(task_spec, 'is_engine_task') or task_spec.is_engine_task()) + def reset_from_task_id(self, task_id, data=None): + """Override method from base class, and assures that if the task + being reset has a boundary event parent, we reset that parent and + run it rather than resetting to the current task. This assures + our boundary events are set to the correct state.""" + + task = self.get_task_from_id(task_id) + run_task_at_end = False + + if isinstance(task.parent.task_spec, _BoundaryEventParent): + task = task.parent + run_task_at_end = True # we jumped up one level, so exectute so we are on the correct task as requested. + + descendants = super().reset_from_task_id(task_id, data) + descendant_ids = [t.id for t in descendants] + top = self._get_outermost_workflow() + + delete, reset = [], [] + for sp_id, sp in top.subprocesses.items(): + if sp_id in descendant_ids: + delete.append(sp_id) + delete.extend([t.id for t in sp.get_tasks() if t.id in top.subprocesses]) + if task in sp.get_tasks(): + reset.append(sp_id) + + # Remove any subprocesses for removed tasks + for sp_id in delete: + del top.subprocesses[sp_id] + + # Reset any containing subprocesses + for sp_id in reset: + descendants.extend(self.reset_from_task_id(sp_id)) + sp_task = self.get_task_from_id(sp_id) + sp_task.state = TaskState.WAITING + + if run_task_at_end: + task.run() + + return descendants + + def cancel(self, workflow=None): + + wf = workflow or self + cancelled = Workflow.cancel(wf) + cancelled_ids = [t.id for t in cancelled] + top = self._get_outermost_workflow() + to_cancel = [] + for sp_id, sp in top.subprocesses.items(): + if sp_id in cancelled_ids: + to_cancel.append(sp) + + for sp in to_cancel: + cancelled.extend(self.cancel(sp)) + + return cancelled diff --git a/SpiffWorkflow/SpiffWorkflow/camunda/__init__.py b/SpiffWorkflow/SpiffWorkflow/camunda/__init__.py index e69de29bb..b93acc8d9 100644 --- a/SpiffWorkflow/SpiffWorkflow/camunda/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/camunda/__init__.py @@ -0,0 +1,18 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/camunda/parser/CamundaParser.py b/SpiffWorkflow/SpiffWorkflow/camunda/parser/CamundaParser.py index 96cc4eb01..46958ffb5 100644 --- a/SpiffWorkflow/SpiffWorkflow/camunda/parser/CamundaParser.py +++ b/SpiffWorkflow/SpiffWorkflow/camunda/parser/CamundaParser.py @@ -1,14 +1,40 @@ - -from SpiffWorkflow.bpmn.parser.BpmnParser import full_tag, DEFAULT_NSMAP - -from SpiffWorkflow.bpmn.specs.ManualTask import ManualTask -from SpiffWorkflow.bpmn.specs.NoneTask import NoneTask -from SpiffWorkflow.bpmn.specs.ScriptTask import ScriptTask -from SpiffWorkflow.bpmn.specs.SubWorkflowTask import CallActivity, TransactionSubprocess +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser -from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask -from SpiffWorkflow.camunda.specs.UserTask import UserTask +from SpiffWorkflow.bpmn.parser.BpmnParser import full_tag, DEFAULT_NSMAP + +from SpiffWorkflow.bpmn.specs.defaults import ( + ManualTask, + NoneTask, + ScriptTask, + CallActivity, + TransactionSubprocess, + StartEvent, + EndEvent, + IntermediateThrowEvent, + IntermediateCatchEvent, + BoundaryEvent +) +from SpiffWorkflow.camunda.specs.business_rule_task import BusinessRuleTask +from SpiffWorkflow.camunda.specs.user_task import UserTask + from SpiffWorkflow.camunda.parser.task_spec import ( CamundaTaskParser, BusinessRuleTaskParser, @@ -18,10 +44,6 @@ from SpiffWorkflow.camunda.parser.task_spec import ( ScriptTaskParser, CAMUNDA_MODEL_NS ) - -from SpiffWorkflow.bpmn.specs.events.StartEvent import StartEvent -from SpiffWorkflow.bpmn.specs.events.EndEvent import EndEvent -from SpiffWorkflow.bpmn.specs.events.IntermediateEvent import IntermediateThrowEvent, IntermediateCatchEvent, BoundaryEvent from .event_parsers import ( CamundaStartEventParser, CamundaEndEventParser, diff --git a/SpiffWorkflow/SpiffWorkflow/camunda/parser/__init__.py b/SpiffWorkflow/SpiffWorkflow/camunda/parser/__init__.py index e69de29bb..b93acc8d9 100644 --- a/SpiffWorkflow/SpiffWorkflow/camunda/parser/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/camunda/parser/__init__.py @@ -0,0 +1,18 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/camunda/parser/event_parsers.py b/SpiffWorkflow/SpiffWorkflow/camunda/parser/event_parsers.py index 04e886c9f..959ace4bc 100644 --- a/SpiffWorkflow/SpiffWorkflow/camunda/parser/event_parsers.py +++ b/SpiffWorkflow/SpiffWorkflow/camunda/parser/event_parsers.py @@ -1,7 +1,31 @@ -from SpiffWorkflow.bpmn.parser.event_parsers import EventDefinitionParser -from SpiffWorkflow.bpmn.parser.event_parsers import StartEventParser, EndEventParser, \ - IntermediateCatchEventParser, IntermediateThrowEventParser, BoundaryEventParser -from SpiffWorkflow.camunda.specs.events.event_definitions import MessageEventDefinition +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +from SpiffWorkflow.bpmn.parser.event_parsers import ( + EventDefinitionParser, + StartEventParser, + EndEventParser, + IntermediateCatchEventParser, + IntermediateThrowEventParser, + BoundaryEventParser +) +from SpiffWorkflow.camunda.specs.event_definitions import MessageEventDefinition from SpiffWorkflow.bpmn.parser.util import one diff --git a/SpiffWorkflow/SpiffWorkflow/camunda/parser/task_spec.py b/SpiffWorkflow/SpiffWorkflow/camunda/parser/task_spec.py index 3cc7e87f3..5a964c1ac 100644 --- a/SpiffWorkflow/SpiffWorkflow/camunda/parser/task_spec.py +++ b/SpiffWorkflow/SpiffWorkflow/camunda/parser/task_spec.py @@ -1,4 +1,21 @@ -from ...camunda.specs.UserTask import Form, FormField, EnumFormField +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA from SpiffWorkflow.bpmn.specs.data_spec import TaskDataReference from SpiffWorkflow.bpmn.parser.util import one @@ -6,9 +23,9 @@ from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException from SpiffWorkflow.bpmn.parser.TaskParser import TaskParser from SpiffWorkflow.bpmn.parser.task_parsers import SubprocessParser -from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask - +from SpiffWorkflow.camunda.specs.business_rule_task import BusinessRuleTask from SpiffWorkflow.camunda.specs.multiinstance_task import SequentialMultiInstanceTask, ParallelMultiInstanceTask +from SpiffWorkflow.camunda.specs.user_task import Form, FormField, EnumFormField CAMUNDA_MODEL_NS = 'http://camunda.org/schema/1.0/bpmn' @@ -66,11 +83,9 @@ class BusinessRuleTaskParser(CamundaTaskParser): def create_task(self): decision_ref = self.get_decision_ref(self.node) - return BusinessRuleTask(self.spec, self.get_task_spec_name(), + return BusinessRuleTask(self.spec, self.bpmn_id, dmnEngine=self.process_parser.parser.get_engine(decision_ref, self.node), - lane=self.lane, position=self.position, - description=self.node.get('name', None), - ) + **self.bpmn_attributes) @staticmethod def get_decision_ref(node): @@ -82,10 +97,7 @@ class UserTaskParser(CamundaTaskParser): def create_task(self): form = self.get_form() - return self.spec_class(self.spec, self.get_task_spec_name(), form, - lane=self.lane, - position=self.position, - description=self.node.get('name', None)) + return self.spec_class(self.spec, self.bpmn_id, form=form, **self.bpmn_attributes) def get_form(self): """Camunda provides a simple form builder, this will extract the @@ -138,10 +150,7 @@ class SubWorkflowParser(CamundaTaskParser): def create_task(self): subworkflow_spec = SubprocessParser.get_subprocess_spec(self) - return self.spec_class( - self.spec, self.get_task_spec_name(), subworkflow_spec, - lane=self.lane, position=self.position, - description=self.node.get('name', None)) + return self.spec_class(self.spec, self.bpmn_id, subworkflow_spec=subworkflow_spec, **self.bpmn_attributes) class CallActivityParser(CamundaTaskParser): @@ -149,10 +158,7 @@ class CallActivityParser(CamundaTaskParser): def create_task(self): subworkflow_spec = SubprocessParser.get_call_activity_spec(self) - return self.spec_class( - self.spec, self.get_task_spec_name(), subworkflow_spec, - lane=self.lane, position=self.position, - description=self.node.get('name', None)) + return self.spec_class(self.spec, self.bpmn_id, subworkflow_spec=subworkflow_spec, **self.bpmn_attributes) class ScriptTaskParser(TaskParser): @@ -162,10 +168,7 @@ class ScriptTaskParser(TaskParser): def create_task(self): script = self.get_script() - return self.spec_class(self.spec, self.get_task_spec_name(), script, - lane=self.lane, - position=self.position, - description=self.node.get('name', None)) + return self.spec_class(self.spec, self.bpmn_id, script=script, **self.bpmn_attributes) def get_script(self): """ @@ -177,5 +180,5 @@ class ScriptTaskParser(TaskParser): return one(self.xpath('.//bpmn:script')).text except AssertionError as ae: raise ValidationException( - f"Invalid Script Task. No Script Provided. " + str(ae), + "Invalid Script Task. No Script Provided. " + str(ae), node=self.node, file_name=self.filename) diff --git a/SpiffWorkflow/SpiffWorkflow/camunda/serializer/__init__.py b/SpiffWorkflow/SpiffWorkflow/camunda/serializer/__init__.py index e69de29bb..b93acc8d9 100644 --- a/SpiffWorkflow/SpiffWorkflow/camunda/serializer/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/camunda/serializer/__init__.py @@ -0,0 +1,18 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/camunda/serializer/config.py b/SpiffWorkflow/SpiffWorkflow/camunda/serializer/config.py index 342512d66..53ac29b2e 100644 --- a/SpiffWorkflow/SpiffWorkflow/camunda/serializer/config.py +++ b/SpiffWorkflow/SpiffWorkflow/camunda/serializer/config.py @@ -1,3 +1,22 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from copy import deepcopy from SpiffWorkflow.bpmn.serializer.workflow import DEFAULT_SPEC_CONFIG @@ -8,8 +27,12 @@ from SpiffWorkflow.bpmn.serializer.task_spec import ( ) from SpiffWorkflow.bpmn.serializer.event_definition import MessageEventDefinitionConverter as DefaultMessageEventConverter - -from .task_spec import UserTaskConverter, ParallelMultiInstanceTaskConverter, SequentialMultiInstanceTaskConverter +from .task_spec import ( + UserTaskConverter, + BusinessRuleTaskConverter, + ParallelMultiInstanceTaskConverter, + SequentialMultiInstanceTaskConverter +) from .event_definition import MessageEventDefinitionConverter @@ -20,6 +43,7 @@ CAMUNDA_SPEC_CONFIG['task_specs'].remove(DefaultParallelMIConverter) CAMUNDA_SPEC_CONFIG['task_specs'].append(ParallelMultiInstanceTaskConverter) CAMUNDA_SPEC_CONFIG['task_specs'].remove(DefaultSequentialMIConverter) CAMUNDA_SPEC_CONFIG['task_specs'].append(SequentialMultiInstanceTaskConverter) +CAMUNDA_SPEC_CONFIG['task_specs'].append(BusinessRuleTaskConverter) CAMUNDA_SPEC_CONFIG['event_definitions'].remove(DefaultMessageEventConverter) CAMUNDA_SPEC_CONFIG['event_definitions'].append(MessageEventDefinitionConverter) diff --git a/SpiffWorkflow/SpiffWorkflow/camunda/serializer/event_definition.py b/SpiffWorkflow/SpiffWorkflow/camunda/serializer/event_definition.py index 8efb16a03..17ace4418 100644 --- a/SpiffWorkflow/SpiffWorkflow/camunda/serializer/event_definition.py +++ b/SpiffWorkflow/SpiffWorkflow/camunda/serializer/event_definition.py @@ -1,5 +1,24 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from SpiffWorkflow.bpmn.serializer.helpers.spec import EventDefinitionConverter -from ..specs.events.event_definitions import MessageEventDefinition +from ..specs.event_definitions import MessageEventDefinition class MessageEventDefinitionConverter(EventDefinitionConverter): diff --git a/SpiffWorkflow/SpiffWorkflow/camunda/serializer/task_spec.py b/SpiffWorkflow/SpiffWorkflow/camunda/serializer/task_spec.py index 6565e1522..598fc8d11 100644 --- a/SpiffWorkflow/SpiffWorkflow/camunda/serializer/task_spec.py +++ b/SpiffWorkflow/SpiffWorkflow/camunda/serializer/task_spec.py @@ -1,7 +1,28 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from SpiffWorkflow.bpmn.serializer.helpers.spec import TaskSpecConverter from SpiffWorkflow.bpmn.serializer.task_spec import MultiInstanceTaskConverter +from SpiffWorkflow.dmn.serializer.task_spec import BaseBusinessRuleTaskConverter -from SpiffWorkflow.camunda.specs.UserTask import UserTask, Form +from SpiffWorkflow.camunda.specs.user_task import UserTask, Form +from SpiffWorkflow.camunda.specs.business_rule_task import BusinessRuleTask from SpiffWorkflow.camunda.specs.multiinstance_task import ParallelMultiInstanceTask, SequentialMultiInstanceTask class UserTaskConverter(TaskSpecConverter): @@ -11,7 +32,6 @@ class UserTaskConverter(TaskSpecConverter): def to_dict(self, spec): dct = self.get_default_attributes(spec) - dct.update(self.get_bpmn_attributes(spec)) dct['form'] = self.form_to_dict(spec.form) return dct @@ -36,6 +56,10 @@ class UserTaskConverter(TaskSpecConverter): return dct +class BusinessRuleTaskConverter(BaseBusinessRuleTaskConverter): + def __init__(self, registry): + super().__init__(BusinessRuleTask, registry) + class ParallelMultiInstanceTaskConverter(MultiInstanceTaskConverter): def __init__(self, registry): super().__init__(ParallelMultiInstanceTask, registry) diff --git a/SpiffWorkflow/SpiffWorkflow/camunda/specs/__init__.py b/SpiffWorkflow/SpiffWorkflow/camunda/specs/__init__.py index e69de29bb..b93acc8d9 100644 --- a/SpiffWorkflow/SpiffWorkflow/camunda/specs/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/camunda/specs/__init__.py @@ -0,0 +1,18 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/camunda/specs/business_rule_task.py b/SpiffWorkflow/SpiffWorkflow/camunda/specs/business_rule_task.py new file mode 100644 index 000000000..799cfba8b --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/camunda/specs/business_rule_task.py @@ -0,0 +1,5 @@ +from SpiffWorkflow.dmn.specs.business_rule_task_mixin import BusinessRuleTaskMixin +from SpiffWorkflow.bpmn.specs.mixins.bpmn_spec_mixin import BpmnSpecMixin + +class BusinessRuleTask(BusinessRuleTaskMixin, BpmnSpecMixin): + pass \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/camunda/specs/events/event_definitions.py b/SpiffWorkflow/SpiffWorkflow/camunda/specs/event_definitions.py similarity index 67% rename from SpiffWorkflow/SpiffWorkflow/camunda/specs/events/event_definitions.py rename to SpiffWorkflow/SpiffWorkflow/camunda/specs/event_definitions.py index e98d29707..57f07ba65 100644 --- a/SpiffWorkflow/SpiffWorkflow/camunda/specs/events/event_definitions.py +++ b/SpiffWorkflow/SpiffWorkflow/camunda/specs/event_definitions.py @@ -1,4 +1,23 @@ -from SpiffWorkflow.bpmn.specs.events.event_definitions import MessageEventDefinition +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +from SpiffWorkflow.bpmn.specs.event_definitions import MessageEventDefinition class MessageEventDefinition(MessageEventDefinition): """ @@ -10,9 +29,9 @@ class MessageEventDefinition(MessageEventDefinition): # this should be revisited: for one thing, we're relying on some Camunda-specific # properties. - def __init__(self, name, correlation_properties=None, payload=None, result_var=None): + def __init__(self, name, correlation_properties=None, payload=None, result_var=None, **kwargs): - super(MessageEventDefinition, self).__init__(name, correlation_properties) + super(MessageEventDefinition, self).__init__(name, correlation_properties, **kwargs) self.payload = payload self.result_var = result_var diff --git a/SpiffWorkflow/SpiffWorkflow/camunda/specs/multiinstance_task.py b/SpiffWorkflow/SpiffWorkflow/camunda/specs/multiinstance_task.py index 9289c6d7f..948cdc40c 100644 --- a/SpiffWorkflow/SpiffWorkflow/camunda/specs/multiinstance_task.py +++ b/SpiffWorkflow/SpiffWorkflow/camunda/specs/multiinstance_task.py @@ -1,8 +1,27 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from SpiffWorkflow.task import TaskState -from SpiffWorkflow.bpmn.specs.BpmnSpecMixin import BpmnSpecMixin +from SpiffWorkflow.bpmn.specs.mixins.bpmn_spec_mixin import BpmnSpecMixin from SpiffWorkflow.bpmn.specs.data_spec import TaskDataReference -from SpiffWorkflow.bpmn.specs.MultiInstanceTask import ( +from SpiffWorkflow.bpmn.specs.defaults import ( SequentialMultiInstanceTask as BpmnSequentialMITask, ParallelMultiInstanceTask as BpmnParallelMITask, ) @@ -20,19 +39,19 @@ def update_task_spec(my_task): if task_spec.cardinality is None: # Use the same collection for input and output - task_spec.data_input = TaskDataReference(task_spec.data_output.name) - task_spec.input_item = TaskDataReference(task_spec.output_item.name) + task_spec.data_input = TaskDataReference(task_spec.data_output.bpmn_id) + task_spec.input_item = TaskDataReference(task_spec.output_item.bpmn_id) else: cardinality = my_task.workflow.script_engine.evaluate(my_task, task_spec.cardinality) if not isinstance(cardinality, int): # The input data was supplied via "cardinality" # We'll use the same reference for input and output item task_spec.data_input = TaskDataReference(task_spec.cardinality) - task_spec.input_item = TaskDataReference(task_spec.output_item.name) if task_spec.output_item is not None else None + task_spec.input_item = TaskDataReference(task_spec.output_item.bpmn_id) if task_spec.output_item is not None else None task_spec.cardinality = None else: # This will be the index - task_spec.input_item = TaskDataReference(task_spec.output_item.name) if task_spec.output_item is not None else None + task_spec.input_item = TaskDataReference(task_spec.output_item.bpmn_id) if task_spec.output_item is not None else None class SequentialMultiInstanceTask(BpmnSequentialMITask): diff --git a/SpiffWorkflow/SpiffWorkflow/camunda/specs/UserTask.py b/SpiffWorkflow/SpiffWorkflow/camunda/specs/user_task.py similarity index 76% rename from SpiffWorkflow/SpiffWorkflow/camunda/specs/UserTask.py rename to SpiffWorkflow/SpiffWorkflow/camunda/specs/user_task.py index 6a952cf2a..fdd43bdb5 100644 --- a/SpiffWorkflow/SpiffWorkflow/camunda/specs/UserTask.py +++ b/SpiffWorkflow/SpiffWorkflow/camunda/specs/user_task.py @@ -1,11 +1,26 @@ -# -*- coding: utf-8 -*- +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +from SpiffWorkflow.bpmn.specs.defaults import UserTask as DefaultUserTask -from ...bpmn.specs.UserTask import UserTask -from ...bpmn.specs.BpmnSpecMixin import BpmnSpecMixin - - -class UserTask(UserTask, BpmnSpecMixin): +class UserTask(DefaultUserTask): """Task Spec for a bpmn:userTask node with Camunda forms.""" def __init__(self, wf_spec, name, form, **kwargs): @@ -17,12 +32,6 @@ class UserTask(UserTask, BpmnSpecMixin): super(UserTask, self).__init__(wf_spec, name, **kwargs) self.form = form - def _on_trigger(self, my_task): - pass - - def is_engine_task(self): - return False - class FormField(object): def __init__(self, form_type="text"): diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/__init__.py b/SpiffWorkflow/SpiffWorkflow/dmn/__init__.py index e69de29bb..b93acc8d9 100644 --- a/SpiffWorkflow/SpiffWorkflow/dmn/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/dmn/__init__.py @@ -0,0 +1,18 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/engine/DMNEngine.py b/SpiffWorkflow/SpiffWorkflow/dmn/engine/DMNEngine.py index dcf7880cc..0601e00c9 100644 --- a/SpiffWorkflow/SpiffWorkflow/dmn/engine/DMNEngine.py +++ b/SpiffWorkflow/SpiffWorkflow/dmn/engine/DMNEngine.py @@ -1,10 +1,29 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + import logging import re +from SpiffWorkflow.exceptions import SpiffWorkflowException +from SpiffWorkflow.bpmn.exceptions import WorkflowTaskException + from ..specs.model import HitPolicy -from ...exceptions import SpiffWorkflowException, WorkflowTaskException -from ...util import levenshtein -from ...workflow import WorkflowException logger = logging.getLogger('spiff.dmn') @@ -37,7 +56,7 @@ class DMNEngine: for rule in matched_rules: rule_output = rule.output_as_dict(task) for key in rule_output.keys(): - if not key in result: + if key not in result: result[key] = [] result[key].append(rule_output[key]) elif len(matched_rules) > 0: @@ -98,7 +117,7 @@ class DMNEngine: # NOTE: It should only do this replacement outside of quotes. # for example, provided "This thing?" in quotes, it should not # do the replacement. - match_expr = re.sub('(\?)(?=(?:[^\'"]|[\'"][^\'"]*[\'"])*$)', 'dmninputexpr', match_expr) + match_expr = re.sub(r'(\?)(?=(?:[^\'"]|[\'"][^\'"]*[\'"])*$)', 'dmninputexpr', match_expr) if 'dmninputexpr' in match_expr: external_methods = { 'dmninputexpr': script_engine.evaluate(task, input_expr) diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/engine/__init__.py b/SpiffWorkflow/SpiffWorkflow/dmn/engine/__init__.py index e69de29bb..b93acc8d9 100644 --- a/SpiffWorkflow/SpiffWorkflow/dmn/engine/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/dmn/engine/__init__.py @@ -0,0 +1,18 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/parser/BpmnDmnParser.py b/SpiffWorkflow/SpiffWorkflow/dmn/parser/BpmnDmnParser.py index 39129be2e..155e4b6cb 100644 --- a/SpiffWorkflow/SpiffWorkflow/dmn/parser/BpmnDmnParser.py +++ b/SpiffWorkflow/SpiffWorkflow/dmn/parser/BpmnDmnParser.py @@ -1,3 +1,22 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + import glob import os @@ -53,7 +72,7 @@ class BpmnDmnParser(BpmnParser): validator.validate(node, filename) dmn_parser = DMNParser(self, node, nsmap, filename=filename) - self.dmn_parsers[dmn_parser.get_id()] = dmn_parser + self.dmn_parsers[dmn_parser.bpmn_id] = dmn_parser self.dmn_parsers_by_name[dmn_parser.get_name()] = dmn_parser def add_dmn_file(self, filename): @@ -75,7 +94,19 @@ class BpmnDmnParser(BpmnParser): """ for filename in filenames: with open(filename, 'r') as f: - self.add_dmn_xml(etree.parse(f).getroot(), filename=filename) + self.add_dmn_io(f, filename=filename) + + def add_dmn_io(self, file_like_object, filename=None): + """ + Add the given DMN file like object to the parser's set. + """ + self.add_dmn_xml(etree.parse(file_like_object).getroot(), filename) + + def add_dmn_str(self, dmn_str, filename=None): + """ + Add the given DMN string to the parser's set. + """ + self.add_dmn_xml(etree.fromstring(dmn_str), filename) def get_dependencies(self): return self.process_dependencies.union(self.dmn_dependencies) diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/parser/DMNParser.py b/SpiffWorkflow/SpiffWorkflow/dmn/parser/DMNParser.py index d08cef53b..8e7b55459 100644 --- a/SpiffWorkflow/SpiffWorkflow/dmn/parser/DMNParser.py +++ b/SpiffWorkflow/SpiffWorkflow/dmn/parser/DMNParser.py @@ -1,12 +1,38 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + import ast from SpiffWorkflow.bpmn.parser.node_parser import NodeParser, DEFAULT_NSMAP -from ...bpmn.parser.ValidationException import ValidationException +from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException -from ...bpmn.parser.util import xpath_eval +from SpiffWorkflow.bpmn.parser.util import xpath_eval -from ...dmn.specs.model import Decision, DecisionTable, InputEntry, \ - OutputEntry, Input, Output, Rule +from SpiffWorkflow.dmn.specs.model import ( + Decision, + DecisionTable, + InputEntry, + OutputEntry, + Input, + Output, + Rule, +) def get_dmn_ns(node): """ @@ -55,7 +81,8 @@ class DMNParser(NodeParser): def parse(self): self.decision = self._parse_decision(self.node.findall('{*}decision')) - def get_id(self): + @property + def bpmn_id(self): """ Returns the process ID """ @@ -172,9 +199,7 @@ class DMNParser(NodeParser): return rule def _parse_input_output_element(self, decision_table, element, cls, idx): - input_or_output = ( - decision_table.inputs if cls == InputEntry else decision_table.outputs if cls == OutputEntry else None)[ - idx] + input_or_output = (decision_table.inputs if cls == InputEntry else decision_table.outputs)[idx] entry = cls(element.attrib['id'], input_or_output) for child in element: if child.tag.endswith('description'): @@ -182,7 +207,8 @@ class DMNParser(NodeParser): elif child.tag.endswith('text'): entry.text = child.text if cls == InputEntry: - entry.lhs.append(entry.text) + # DMN renders 'no input specification' with '-'; assume this is intended if somebody has added '-' + entry.lhs.append(entry.text if entry.text != '-' else None) elif cls == OutputEntry: if entry.text and entry.text != '': try: diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/parser/__init__.py b/SpiffWorkflow/SpiffWorkflow/dmn/parser/__init__.py index e69de29bb..b93acc8d9 100644 --- a/SpiffWorkflow/SpiffWorkflow/dmn/parser/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/dmn/parser/__init__.py @@ -0,0 +1,18 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/serializer/__init__.py b/SpiffWorkflow/SpiffWorkflow/dmn/serializer/__init__.py index e69de29bb..b93acc8d9 100644 --- a/SpiffWorkflow/SpiffWorkflow/dmn/serializer/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/dmn/serializer/__init__.py @@ -0,0 +1,18 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/serializer/task_spec.py b/SpiffWorkflow/SpiffWorkflow/dmn/serializer/task_spec.py index e5a03e992..42e38d11f 100644 --- a/SpiffWorkflow/SpiffWorkflow/dmn/serializer/task_spec.py +++ b/SpiffWorkflow/SpiffWorkflow/dmn/serializer/task_spec.py @@ -1,6 +1,24 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from ...bpmn.serializer.helpers.spec import TaskSpecConverter -from ..specs.BusinessRuleTask import BusinessRuleTask from ..specs.model import DecisionTable, Rule, HitPolicy from ..specs.model import Input, InputEntry, Output, OutputEntry from ..engine.DMNEngine import DMNEngine @@ -9,7 +27,6 @@ class BaseBusinessRuleTaskConverter(TaskSpecConverter): def to_dict(self, spec): dct = self.get_default_attributes(spec) - dct.update(self.get_bpmn_attributes(spec)) # We only ever use one decision table dct['decision_table'] = self.decision_table_to_dict(spec.dmnEngine.decision_table) return dct @@ -95,8 +112,3 @@ class BaseBusinessRuleTaskConverter(TaskSpecConverter): rule.outputEntries = [self.output_entry_from_dict(entry, outputs) for entry in dct['output_entries']] return rule - - -class BusinessRuleTaskConverter(BaseBusinessRuleTaskConverter): - def __init__(self, registry): - super().__init__(BusinessRuleTask, registry) \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/specs/BusinessRuleTask.py b/SpiffWorkflow/SpiffWorkflow/dmn/specs/BusinessRuleTask.py deleted file mode 100644 index 44af82189..000000000 --- a/SpiffWorkflow/SpiffWorkflow/dmn/specs/BusinessRuleTask.py +++ /dev/null @@ -1,37 +0,0 @@ -from SpiffWorkflow.exceptions import WorkflowTaskException, SpiffWorkflowException - -from ...specs.Simple import Simple - -from ...bpmn.specs.BpmnSpecMixin import BpmnSpecMixin -from ...util.deep_merge import DeepMerge - - -class BusinessRuleTask(Simple, BpmnSpecMixin): - """ - Task Spec for a bpmn:businessTask (DMB Decision Reference) node. - """ - - def _on_trigger(self, my_task): - pass - - def __init__(self, wf_spec, name, dmnEngine, **kwargs): - super().__init__(wf_spec, name, **kwargs) - self.dmnEngine = dmnEngine - self.resDict = None - - @property - def spec_class(self): - return 'Business Rule Task' - - def _run_hook(self, my_task): - try: - my_task.data = DeepMerge.merge(my_task.data, self.dmnEngine.result(my_task)) - super(BusinessRuleTask, self)._run_hook(my_task) - except SpiffWorkflowException as we: - we.add_note(f"Business Rule Task '{my_task.task_spec.description}'.") - raise we - except Exception as e: - error = WorkflowTaskException(str(e), task=my_task) - error.add_note(f"Business Rule Task '{my_task.task_spec.description}'.") - raise error - return True diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/specs/__init__.py b/SpiffWorkflow/SpiffWorkflow/dmn/specs/__init__.py index e69de29bb..b93acc8d9 100644 --- a/SpiffWorkflow/SpiffWorkflow/dmn/specs/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/dmn/specs/__init__.py @@ -0,0 +1,18 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/specs/business_rule_task_mixin.py b/SpiffWorkflow/SpiffWorkflow/dmn/specs/business_rule_task_mixin.py new file mode 100644 index 000000000..45ff766f1 --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/dmn/specs/business_rule_task_mixin.py @@ -0,0 +1,49 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +from SpiffWorkflow.exceptions import SpiffWorkflowException +from SpiffWorkflow.bpmn.exceptions import WorkflowTaskException +from SpiffWorkflow.specs.base import TaskSpec +from SpiffWorkflow.util.deep_merge import DeepMerge + + +class BusinessRuleTaskMixin(TaskSpec): + """Task Spec for a bpmn:businessTask (DMB Decision Reference) node.""" + + def __init__(self, wf_spec, name, dmnEngine, **kwargs): + super().__init__(wf_spec, name, **kwargs) + self.dmnEngine = dmnEngine + self.resDict = None + + @property + def spec_class(self): + return 'Business Rule Task' + + def _run_hook(self, my_task): + try: + my_task.data = DeepMerge.merge(my_task.data, self.dmnEngine.result(my_task)) + super(BusinessRuleTaskMixin, self)._run_hook(my_task) + except SpiffWorkflowException as we: + we.add_note(f"Business Rule Task '{my_task.task_spec.bpmn_name}'.") + raise we + except Exception as e: + error = WorkflowTaskException(str(e), task=my_task) + error.add_note(f"Business Rule Task '{my_task.task_spec.bpmn_name}'.") + raise error + return True diff --git a/SpiffWorkflow/SpiffWorkflow/dmn/specs/model.py b/SpiffWorkflow/SpiffWorkflow/dmn/specs/model.py index bf3322d18..9b35bfe5c 100644 --- a/SpiffWorkflow/SpiffWorkflow/dmn/specs/model.py +++ b/SpiffWorkflow/SpiffWorkflow/dmn/specs/model.py @@ -1,3 +1,22 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from collections import OrderedDict from enum import Enum diff --git a/SpiffWorkflow/SpiffWorkflow/exceptions.py b/SpiffWorkflow/SpiffWorkflow/exceptions.py index 3b3350620..3320d0542 100644 --- a/SpiffWorkflow/SpiffWorkflow/exceptions.py +++ b/SpiffWorkflow/SpiffWorkflow/exceptions.py @@ -1,12 +1,13 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2007 Samuel Abels +# Copyright (C) 2007 Samuel Abels, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -15,10 +16,6 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -import re - -from SpiffWorkflow.util import levenshtein - class SpiffWorkflowException(Exception): """ @@ -55,81 +52,6 @@ class WorkflowException(SpiffWorkflowException): # Points to the TaskSpec that generated the exception. self.task_spec = task_spec - @staticmethod - def get_task_trace(task): - task_trace = [f"{task.task_spec.description} ({task.workflow.spec.file})"] - workflow = task.workflow - while workflow != workflow.outer_workflow: - caller = workflow.name - workflow = workflow.outer_workflow - task_trace.append(f"{workflow.spec.task_specs[caller].description} ({workflow.spec.file})") - return task_trace - - @staticmethod - def did_you_mean_from_name_error(name_exception, options): - """Returns a string along the lines of 'did you mean 'dog'? Given - a name_error, and a set of possible things that could have been called, - or an empty string if no valid suggestions come up. """ - if isinstance(name_exception, NameError): - def_match = re.match("name '(.+)' is not defined", str(name_exception)) - if def_match: - bad_variable = re.match("name '(.+)' is not defined", - str(name_exception)).group(1) - most_similar = levenshtein.most_similar(bad_variable, - options, 3) - error_msg = "" - if len(most_similar) == 1: - error_msg += f' Did you mean \'{most_similar[0]}\'?' - if len(most_similar) > 1: - error_msg += f' Did you mean one of \'{most_similar}\'?' - return error_msg - - -class WorkflowTaskException(WorkflowException): - """WorkflowException that provides task_trace information.""" - - def __init__(self, error_msg, task=None, exception=None, - line_number=None, offset=None, error_line=None): - """ - Exception initialization. - - :param task: the task that threw the exception - :type task: Task - :param error_msg: a human readable error message - :type error_msg: str - :param exception: an exception to wrap, if any - :type exception: Exception - """ - - self.task = task - self.line_number = line_number - self.offset = offset - self.error_line = error_line - if exception: - self.error_type = exception.__class__.__name__ - else: - self.error_type = "unknown" - super().__init__(error_msg, task_spec=task.task_spec) - - if isinstance(exception, SyntaxError) and not line_number: - # Line number and offset can be recovered directly from syntax errors, - # otherwise they must be passed in. - self.line_number = exception.lineno - self.offset = exception.offset - elif isinstance(exception, NameError): - self.add_note(self.did_you_mean_from_name_error(exception, list(task.data.keys()))) - - # If encountered in a sub-workflow, this traces back up the stack, - # so we can tell how we got to this particular task, no matter how - # deeply nested in sub-workflows it is. Takes the form of: - # task-description (file-name) - self.task_trace = self.get_task_trace(task) - - - -class StorageException(SpiffWorkflowException): - pass - class TaskNotFoundException(WorkflowException): pass diff --git a/SpiffWorkflow/SpiffWorkflow/operators.py b/SpiffWorkflow/SpiffWorkflow/operators.py index a5913b123..b9f43cb66 100644 --- a/SpiffWorkflow/SpiffWorkflow/operators.py +++ b/SpiffWorkflow/SpiffWorkflow/operators.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2007 Samuel Abels # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,6 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + import logging import re @@ -198,8 +199,8 @@ def valueof(scope, op, default=None): def is_number(text): try: - x = int(text) - except: + int(text) + except Exception: return False return True diff --git a/SpiffWorkflow/SpiffWorkflow/serializer/__init__.py b/SpiffWorkflow/SpiffWorkflow/serializer/__init__.py index e69de29bb..d50ddaba8 100644 --- a/SpiffWorkflow/SpiffWorkflow/serializer/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/serializer/__init__.py @@ -0,0 +1,16 @@ +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/serializer/base.py b/SpiffWorkflow/SpiffWorkflow/serializer/base.py index 17c7bdc24..6dd707a8b 100644 --- a/SpiffWorkflow/SpiffWorkflow/serializer/base.py +++ b/SpiffWorkflow/SpiffWorkflow/serializer/base.py @@ -1,12 +1,11 @@ -# -*- coding: utf-8 -*- - -from builtins import object -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -20,7 +19,6 @@ from .. import operators from ..specs.AcquireMutex import AcquireMutex from ..specs.Cancel import Cancel from ..specs.CancelTask import CancelTask -from ..specs.Celery import Celery from ..specs.Choose import Choose from ..specs.ExclusiveChoice import ExclusiveChoice from ..specs.Execute import Execute @@ -46,7 +44,6 @@ def spec_map(): 'acquire-mutex': AcquireMutex, 'cancel': Cancel, 'cancel-task': CancelTask, - 'celery': Celery, 'choose': Choose, 'exclusive-choice': ExclusiveChoice, 'execute': Execute, diff --git a/SpiffWorkflow/SpiffWorkflow/serializer/dict.py b/SpiffWorkflow/SpiffWorkflow/serializer/dict.py index d9108dbf8..168f4deda 100644 --- a/SpiffWorkflow/SpiffWorkflow/serializer/dict.py +++ b/SpiffWorkflow/SpiffWorkflow/serializer/dict.py @@ -1,14 +1,11 @@ -# -*- coding: utf-8 -*- - - -import json -from builtins import str -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -17,17 +14,19 @@ from builtins import str # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + +import json + import pickle from base64 import b64encode, b64decode from ..workflow import Workflow from ..util.impl import get_class from ..task import Task, TaskState -from ..operators import (Attrib, PathAttrib, Equal, NotEqual, Operator, GreaterThan, LessThan, Match) +from ..operators import Attrib, PathAttrib, Equal, NotEqual, Operator, GreaterThan, LessThan, Match from ..specs.base import TaskSpec from ..specs.AcquireMutex import AcquireMutex from ..specs.Cancel import Cancel from ..specs.CancelTask import CancelTask -from ..specs.Celery import Celery from ..specs.Choose import Choose from ..specs.ExclusiveChoice import ExclusiveChoice from ..specs.Execute import Execute @@ -51,25 +50,17 @@ import warnings class DictionarySerializer(Serializer): - def __init__(self): - # When deserializing, this is a set of specs for sub-workflows. - # This prevents us from serializing a copy of the same spec many - # times, which can create very large files. - self.SPEC_STATES = {} - def serialize_dict(self, thedict): return dict( - (str(k), b64encode(pickle.dumps(v, - protocol=pickle.HIGHEST_PROTOCOL))) - for k, v in list(thedict.items())) + (str(k), b64encode(pickle.dumps(v, protocol=pickle.HIGHEST_PROTOCOL))) + for k, v in list(thedict.items()) + ) def deserialize_dict(self, s_state): - return dict((k, pickle.loads(b64decode(v))) - for k, v in list(s_state.items())) + return dict((k, pickle.loads(b64decode(v))) for k, v in list(s_state.items())) def serialize_list(self, thelist): - return [b64encode(pickle.dumps(v, protocol=pickle.HIGHEST_PROTOCOL)) - for v in thelist] + return [b64encode(pickle.dumps(v, protocol=pickle.HIGHEST_PROTOCOL)) for v in thelist] def deserialize_list(self, s_state): return [pickle.loads(b64decode(v)) for v in s_state] @@ -149,48 +140,34 @@ class DictionarySerializer(Serializer): return ret def serialize_task_spec(self, spec): - s_state = dict(id=spec.id, - name=spec.name, + s_state = dict(name=spec.name, description=spec.description, manual=spec.manual, - internal=spec.internal, lookahead=spec.lookahead) module_name = spec.__class__.__module__ s_state['class'] = module_name + '.' + spec.__class__.__name__ - s_state['inputs'] = [t.id for t in spec.inputs] - s_state['outputs'] = [t.id for t in spec.outputs] + s_state['inputs'] = [t.name for t in spec.inputs] + s_state['outputs'] = [t.name for t in spec.outputs] s_state['data'] = self.serialize_dict(spec.data) - if hasattr(spec, 'position'): - s_state['position'] = self.serialize_dict(spec.position) - s_state['defines'] = self.serialize_dict(spec.defines) s_state['pre_assign'] = self.serialize_list(spec.pre_assign) s_state['post_assign'] = self.serialize_list(spec.post_assign) - # Note: Events are not serialized; this is documented in # the TaskSpec API docs. - return s_state def deserialize_task_spec(self, wf_spec, s_state, spec): - spec.id = s_state.get('id', None) spec.description = s_state.get('description', '') spec.manual = s_state.get('manual', False) - spec.internal = s_state.get('internal', False) spec.lookahead = s_state.get('lookahead', 2) - spec.data = self.deserialize_dict(s_state.get('data', {})) - if 'position' in s_state.keys(): - spec.position = self.deserialize_dict(s_state.get('position', {})) spec.defines = self.deserialize_dict(s_state.get('defines', {})) spec.pre_assign = self.deserialize_list(s_state.get('pre_assign', [])) - spec.post_assign = self.deserialize_list( - s_state.get('post_assign', [])) + spec.post_assign = self.deserialize_list(s_state.get('post_assign', [])) # We can't restore inputs and outputs yet because they may not be # deserialized yet. So keep the names, and resolve them in the end. spec.inputs = s_state.get('inputs', [])[:] spec.outputs = s_state.get('outputs', [])[:] - return spec def serialize_acquire_mutex(self, spec): @@ -210,8 +187,7 @@ class DictionarySerializer(Serializer): return s_state def deserialize_cancel(self, wf_spec, s_state): - spec = Cancel(wf_spec, s_state['name'], - success=s_state.get('cancel_successfully', False)) + spec = Cancel(wf_spec, s_state['name'], success=s_state.get('cancel_successfully', False)) self.deserialize_task_spec(wf_spec, s_state, spec=spec) return spec @@ -226,26 +202,6 @@ class DictionarySerializer(Serializer): self.deserialize_task_spec(wf_spec, s_state, spec=spec) return spec - def serialize_celery(self, spec): - args = self.serialize_list(spec.args) - kwargs = self.serialize_dict(spec.kwargs) - s_state = self.serialize_task_spec(spec) - s_state['call'] = spec.call - s_state['args'] = args - s_state['kwargs'] = kwargs - s_state['result_key'] = spec.result_key - return s_state - - def deserialize_celery(self, wf_spec, s_state): - args = self.deserialize_list(s_state['args']) - kwargs = self.deserialize_dict(s_state.get('kwargs', {})) - spec = Celery(wf_spec, s_state['name'], s_state['call'], - call_args=args, - result_key=s_state['result_key'], - **kwargs) - self.deserialize_task_spec(wf_spec, s_state, spec) - return spec - def serialize_choose(self, spec): s_state = self.serialize_task_spec(spec) s_state['context'] = spec.context @@ -305,12 +261,12 @@ class DictionarySerializer(Serializer): s_state['cancel_remaining'] = spec.cancel_remaining return s_state - def deserialize_join(self, wf_spec, s_state, cls=Join): + def deserialize_join(self, wf_spec, s_state): if isinstance(s_state['threshold'],dict): byte_payload = s_state['threshold']['__bytes__'] else: byte_payload = s_state['threshold'] - spec = cls(wf_spec, + spec = Join(wf_spec, s_state['name'], split_task=s_state['split_task'], threshold=pickle.loads(b64decode(byte_payload)), @@ -347,36 +303,25 @@ class DictionarySerializer(Serializer): s_state = self.serialize_task_spec(spec) # here we need to add in all of the things that would get serialized # for other classes that the MultiInstance could be - - # - if isinstance(spec, SubWorkflow): br_state = self.serialize_sub_workflow(spec) s_state['file'] = br_state['file'] s_state['in_assign'] = br_state['in_assign'] s_state['out_assign'] = br_state['out_assign'] - s_state['times'] = self.serialize_arg(spec.times) - s_state['prevtaskclass'] = spec.prevtaskclass return s_state - def deserialize_multi_instance(self, wf_spec, s_state, cls=None): - if cls == None: - cls = MultiInstance(wf_spec, - s_state['name'], - times=self.deserialize_arg(s_state['times'])) - if isinstance(s_state['times'],list): - s_state['times'] = self.deserialize_arg(s_state['times']) - cls.times = s_state['times'] - if isinstance(cls, SubWorkflow): + def deserialize_multi_instance(self, wf_spec, s_state): + spec = MultiInstance(wf_spec, s_state['name'], times=self.deserialize_arg(s_state['times'])) + if isinstance(spec, SubWorkflow): if s_state.get('file'): - cls.file = self.deserialize_arg(s_state['file']) + spec.file = self.deserialize_arg(s_state['file']) else: - cls.file = None - cls.in_assign = self.deserialize_list(s_state['in_assign']) - cls.out_assign = self.deserialize_list(s_state['out_assign']) - - self.deserialize_task_spec(wf_spec, s_state, spec=cls) - return cls + spec.file = None + spec.in_assign = self.deserialize_list(s_state['in_assign']) + spec.out_assign = self.deserialize_list(s_state['out_assign']) + self.deserialize_task_spec(wf_spec, s_state, spec=spec) + return spec def serialize_release_mutex(self, spec): s_state = self.serialize_task_spec(spec) @@ -398,7 +343,6 @@ class DictionarySerializer(Serializer): self.deserialize_task_spec(wf_spec, s_state, spec=spec) return spec - def deserialize_generic(self, wf_spec, s_state,newclass): assert isinstance(wf_spec, WorkflowSpec) spec = newclass(wf_spec, s_state['name']) @@ -486,71 +430,33 @@ class DictionarySerializer(Serializer): return spec def serialize_workflow_spec(self, spec, **kwargs): - s_state = dict(name=spec.name, - description=spec.description, - file=spec.file) - - if 'Root' not in spec.task_specs: - # This is to fix up the case when we - # load in a task spec and there is no root object. - # it causes problems when we deserialize and then re-serialize - # because the deserialize process adds a root. - root = Simple(spec, 'Root') - spec.task_specs['Root'] = root - - mylist = [(k, v.serialize(self)) for k, v in list(spec.task_specs.items())] - - # As we serialize back up, keep only one copy of any sub_workflow - s_state['sub_workflows'] = {} - for name, task in mylist: - if 'spec' in task: - spec = json.loads(task['spec']) - if 'sub_workflows' in spec: - s_state['sub_workflows'].update(spec['sub_workflows']) - del spec['sub_workflows'] - if spec['name'] not in s_state['sub_workflows']: - s_state['sub_workflows'][spec['name']] = json.dumps(spec) - task['spec_name'] = spec['name'] - del task['spec'] - - if hasattr(spec,'end'): - s_state['end']=spec.end.id - s_state['task_specs'] = dict(mylist) + s_state = dict(name=spec.name, description=spec.description, file=spec.file) + s_state['task_specs'] = dict( + (k, v.serialize(self)) + for k, v in list(spec.task_specs.items()) + ) return s_state def _deserialize_workflow_spec_task_spec(self, spec, task_spec, name): - task_spec.inputs = [spec.get_task_spec_from_id(t) for t in task_spec.inputs] - task_spec.outputs = [spec.get_task_spec_from_id(t) for t in task_spec.outputs] - - def _prevtaskclass_bases(self, oldtask): - return (oldtask) + task_spec.inputs = [spec.get_task_spec_from_name(t) for t in task_spec.inputs] + task_spec.outputs = [spec.get_task_spec_from_name(t) for t in task_spec.outputs] def deserialize_workflow_spec(self, s_state, **kwargs): spec = WorkflowSpec(s_state['name'], filename=s_state['file']) spec.description = s_state['description'] + # Handle Start Task spec.start = None - - # Store all sub-workflows so they can be referenced. - if 'sub_workflows' in s_state: - # Hate the whole json dumps thing, why do we do this? - self.SPEC_STATES.update(s_state['sub_workflows']) - del spec.task_specs['Start'] start_task_spec_state = s_state['task_specs']['Start'] start_task_spec = StartTask.deserialize(self, spec, start_task_spec_state) spec.start = start_task_spec + spec.task_specs['Start'] = start_task_spec for name, task_spec_state in list(s_state['task_specs'].items()): if name == 'Start': continue - prevtask = task_spec_state.get('prevtaskclass', None) - if prevtask: - oldtask = get_class(prevtask) - task_spec_cls = type(task_spec_state['class'], - self._prevtaskclass_bases(oldtask), {}) - else: - task_spec_cls = get_class(task_spec_state['class']) + task_spec_cls = get_class(task_spec_state['class']) task_spec = task_spec_cls.deserialize(self, spec, task_spec_state) spec.task_specs[name] = task_spec @@ -558,7 +464,7 @@ class DictionarySerializer(Serializer): self._deserialize_workflow_spec_task_spec(spec, task_spec, name) if s_state.get('end', None): - spec.end = spec.get_task_spec_from_id(s_state['end']) + spec.end = spec.get_task_spec_from_name(s_state['end']) assert spec.start is spec.get_task_spec_from_name('Start') return spec @@ -578,25 +484,19 @@ class DictionarySerializer(Serializer): return s_state - def deserialize_workflow(self, s_state, wf_class=Workflow, wf_spec=None, **kwargs): + def deserialize_workflow(self, s_state, wf_class=Workflow, **kwargs): """It is possible to override the workflow class, and specify a workflow_spec, otherwise the spec is assumed to be serialized in the s_state['wf_spec']""" - if wf_spec is None: - # The json serializer serializes the spec as a string and then serializes it again, hence this check - # I'm not confident that this is going to actually work, but this serializer is so fundamentally flawed - # that I'm not going to put the effort in to be sure this works. - if isinstance(s_state['wf_spec'], str): - spec_dct = json.loads(s_state['wf_spec']) - else: - spec_dct = s_state['wf_spec'] - reset_specs = [spec['name'] for spec in spec_dct['task_specs'].values() if spec['class'].endswith('LoopResetTask')] - for name in reset_specs: - s_state['wf_spec']['task_specs'].pop(name) - wf_spec = self.deserialize_workflow_spec(s_state['wf_spec'], **kwargs) + if isinstance(s_state['wf_spec'], str): + spec_dct = json.loads(s_state['wf_spec']) else: - reset_specs = [] + spec_dct = s_state['wf_spec'] + reset_specs = [spec['name'] for spec in spec_dct['task_specs'].values() if spec['class'].endswith('LoopResetTask')] + for name in reset_specs: + s_state['wf_spec']['task_specs'].pop(name) + wf_spec = self.deserialize_workflow_spec(s_state['wf_spec'], **kwargs) workflow = wf_class(wf_spec) workflow.data = self.deserialize_dict(s_state['data']) @@ -623,35 +523,24 @@ class DictionarySerializer(Serializer): return workflow - def serialize_task(self, task, skip_children=False, allow_subs=False): - """ - :param allow_subs: Allows sub-serialization to take place, otherwise - assumes that the subworkflow is stored in internal data and raises an error. - """ - + def serialize_task(self, task, skip_children=False): assert isinstance(task, Task) - - # Please note, the BPMN Serializer DOES allow sub-workflows. This is - # for backwards compatibility and support of the original parsers. - if not allow_subs and isinstance(task.task_spec, SubWorkflow): + if isinstance(task.task_spec, SubWorkflow): raise TaskNotSupportedError( "Subworkflow tasks cannot be serialized (due to their use of" + " internal_data to store the subworkflow).") - s_state = dict() s_state['id'] = task.id s_state['workflow_name'] = task.workflow.name s_state['parent'] = task.parent.id if task.parent is not None else None if not skip_children: - s_state['children'] = [ - self.serialize_task(child) for child in task.children] + s_state['children'] = [self.serialize_task(child) for child in task.children] s_state['state'] = task.state s_state['triggered'] = task.triggered s_state['task_spec'] = task.task_spec.name s_state['last_state_change'] = task.last_state_change s_state['data'] = self.serialize_dict(task.data) s_state['internal_data'] = task.internal_data - return s_state def deserialize_task(self, workflow, s_state, ignored_specs=None): @@ -664,9 +553,6 @@ class DictionarySerializer(Serializer): raise MissingSpecError("Unknown task spec: " + old_spec_name) task = Task(workflow, task_spec) - if getattr(task_spec,'isSequential',False) and s_state['internal_data'].get('splits') is not None: - task.task_spec.expanded = s_state['internal_data']['splits'] - task.id = s_state['id'] # as the task_tree might not be complete yet # keep the ids so they can be processed at the end diff --git a/SpiffWorkflow/SpiffWorkflow/serializer/dotv.py b/SpiffWorkflow/SpiffWorkflow/serializer/dotv.py index 0d3f0a636..7a7330e24 100644 --- a/SpiffWorkflow/SpiffWorkflow/serializer/dotv.py +++ b/SpiffWorkflow/SpiffWorkflow/serializer/dotv.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- - -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. diff --git a/SpiffWorkflow/SpiffWorkflow/serializer/exceptions.py b/SpiffWorkflow/SpiffWorkflow/serializer/exceptions.py index 73d9e4b4b..166046549 100644 --- a/SpiffWorkflow/SpiffWorkflow/serializer/exceptions.py +++ b/SpiffWorkflow/SpiffWorkflow/serializer/exceptions.py @@ -1,3 +1,20 @@ +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + class TaskSpecNotSupportedError(ValueError): pass diff --git a/SpiffWorkflow/SpiffWorkflow/serializer/json.py b/SpiffWorkflow/SpiffWorkflow/serializer/json.py index a7c28efe7..3bccaac60 100644 --- a/SpiffWorkflow/SpiffWorkflow/serializer/json.py +++ b/SpiffWorkflow/SpiffWorkflow/serializer/json.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- - -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -14,6 +14,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + import json import uuid from ..operators import Attrib diff --git a/SpiffWorkflow/SpiffWorkflow/serializer/prettyxml.py b/SpiffWorkflow/SpiffWorkflow/serializer/prettyxml.py index 7ac8fdfae..f55ba4638 100644 --- a/SpiffWorkflow/SpiffWorkflow/serializer/prettyxml.py +++ b/SpiffWorkflow/SpiffWorkflow/serializer/prettyxml.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2007-2012 Samuel Abels +# Copyright (C) 2007-2012 Samuel Abels, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,6 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + from .. import operators from ..specs.Simple import Simple from ..specs.WorkflowSpec import WorkflowSpec diff --git a/SpiffWorkflow/SpiffWorkflow/serializer/xml.py b/SpiffWorkflow/SpiffWorkflow/serializer/xml.py index c4d32443a..4bbee9224 100644 --- a/SpiffWorkflow/SpiffWorkflow/serializer/xml.py +++ b/SpiffWorkflow/SpiffWorkflow/serializer/xml.py @@ -1,12 +1,11 @@ -# -*- coding: utf-8 -*- - -from builtins import str -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -15,6 +14,7 @@ from builtins import str # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + import warnings from lxml import etree from lxml.etree import SubElement @@ -24,7 +24,6 @@ from ..operators import (Attrib, Assign, PathAttrib, Equal, NotEqual, GreaterTha from ..specs.AcquireMutex import AcquireMutex from ..specs.Cancel import Cancel from ..specs.CancelTask import CancelTask -from ..specs.Celery import Celery from ..specs.Choose import Choose from ..specs.ExclusiveChoice import ExclusiveChoice from ..specs.Execute import Execute @@ -287,15 +286,11 @@ class XmlSerializer(Serializer): """ Serializes common attributes of :meth:`SpiffWorkflow.specs.TaskSpec`. """ - if spec.id is not None: - SubElement(elem, 'id').text = str(spec.id) SubElement(elem, 'name').text = spec.name if spec.description: SubElement(elem, 'description').text = spec.description if spec.manual: SubElement(elem, 'manual') - if spec.internal: - SubElement(elem, 'internal') SubElement(elem, 'lookahead').text = str(spec.lookahead) inputs = [t.name for t in spec.inputs] outputs = [t.name for t in spec.outputs] @@ -316,11 +311,8 @@ class XmlSerializer(Serializer): def deserialize_task_spec(self, wf_spec, elem, spec_cls, **kwargs): name = elem.findtext('name') spec = spec_cls(wf_spec, name, **kwargs) - theid = elem.findtext('id') - spec.id = theid if theid is not None else None spec.description = elem.findtext('description', spec.description) spec.manual = elem.findtext('manual', spec.manual) - spec.internal = elem.find('internal') is not None spec.lookahead = int(elem.findtext('lookahead', spec.lookahead)) data_elem = elem.find('data') @@ -384,37 +376,6 @@ class XmlSerializer(Serializer): def deserialize_cancel_task(self, wf_spec, elem, cls=CancelTask, **kwargs): return self.deserialize_trigger(wf_spec, elem, cls, **kwargs) - def serialize_celery(self, spec, elem=None): - if elem is None: - elem = etree.Element('celery') - - SubElement(elem, 'call').text = spec.call - args_elem = SubElement(elem, 'args') - self.serialize_value_list(args_elem, spec.args) - kwargs_elem = SubElement(elem, 'kwargs') - self.serialize_value_map(kwargs_elem, spec.kwargs) - if spec.merge_results: - SubElement(elem, 'merge-results') - SubElement(elem, 'result-key').text = spec.result_key - - return self.serialize_task_spec(spec, elem) - - def deserialize_celery(self, wf_spec, elem, cls=Celery, **kwargs): - call = elem.findtext('call') - args = self.deserialize_value_list(elem.find('args')) - result_key = elem.findtext('call') - merge_results = elem.find('merge-results') is not None - spec = self.deserialize_task_spec(wf_spec, - elem, - cls, - call=call, - call_args=args, - result_key=result_key, - merge_results=merge_results, - **kwargs) - spec.kwargs = self.deserialize_value_map(elem.find('kwargs')) - return spec - def serialize_choose(self, spec, elem=None): if elem is None: elem = etree.Element('choose') @@ -526,7 +487,7 @@ class XmlSerializer(Serializer): def deserialize_multi_instance(self, wf_spec, elem, cls=None, **kwargs): - if cls == None: + if cls is None: cls = MultiInstance #cls = MultiInstance(wf_spec,elem.find('name'),elem.find('times')) times = self.deserialize_value(elem.find('times')) diff --git a/SpiffWorkflow/SpiffWorkflow/signavio/parser/__init__.py b/SpiffWorkflow/SpiffWorkflow/signavio/parser/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/SpiffWorkflow/SpiffWorkflow/signavio/parser/bpmn.py b/SpiffWorkflow/SpiffWorkflow/signavio/parser/bpmn.py deleted file mode 100644 index dda808b44..000000000 --- a/SpiffWorkflow/SpiffWorkflow/signavio/parser/bpmn.py +++ /dev/null @@ -1,20 +0,0 @@ -from SpiffWorkflow.bpmn.parser.BpmnParser import BpmnParser -from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException -from SpiffWorkflow.bpmn.parser.util import xpath_eval - -class SignavioBpmnParser(BpmnParser): - - def add_bpmn_xml(self, bpmn, filename=None): - # signavio sometimes disconnects a BoundaryEvent from it's owning task - # They then show up as intermediateCatchEvents without any incoming - # sequence flows. Check for this case before parsing the XML. - xpath = xpath_eval(bpmn) - for catch_event in xpath('.//bpmn:intermediateCatchEvent'): - incoming = xpath('.//bpmn:sequenceFlow[@targetRef="%s"]' % catch_event.get('id')) - if not incoming: - raise ValidationException( - 'Intermediate Catch Event has no incoming sequences. ' - 'This might be a Boundary Event that has been ' - 'disconnected.', - node=catch_event, file_name=filename) - return super().add_bpmn_xml(bpmn, filename) diff --git a/SpiffWorkflow/SpiffWorkflow/signavio/parser/tasks.py b/SpiffWorkflow/SpiffWorkflow/signavio/parser/tasks.py deleted file mode 100644 index 5a9ac6f4b..000000000 --- a/SpiffWorkflow/SpiffWorkflow/signavio/parser/tasks.py +++ /dev/null @@ -1,45 +0,0 @@ -from SpiffWorkflow.bpmn.parser.task_parsers import TaskParser -from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException -from SpiffWorkflow.bpmn.parser.util import one, xpath_eval - -SIGNAVIO_NS = 'http://www.signavio.com' - - -class CallActivityParser(TaskParser): - """Parses a CallActivity node.""" - - def create_task(self): - subworkflow_spec = self.get_subprocess_spec() - return self.spec_class( - self.spec, self.get_task_spec_name(), subworkflow_spec, - lane=self.lane, - position=self.position, - description=self.node.get('name', None)) - - def get_subprocess_spec(self): - called_element = self.node.get('calledElement', None) or self._fix_call_activities() - parser = self.process_parser.parser.get_process_parser(called_element) - if parser is None: - raise ValidationException( - f"The process '{called_element}' was not found. Did you mean one of the following: " - f"{', '.join(self.process_parser.parser.get_process_ids())}?", - node=self.node, - file_name=self.process_parser.filename) - return parser.get_id() - - def _fix_call_activities(self): - """ - Signavio produces slightly invalid BPMN for call activity nodes... It - is supposed to put a reference to the id of the called process in to - the calledElement attribute. Instead it stores a string (which is the - name of the process - not its ID, in our interpretation) in an - extension tag. - """ - signavio_meta_data = xpath_eval(self.node, extra_ns={ - 'signavio': SIGNAVIO_NS})( - './/signavio:signavioMetaData[@metaKey="entry"]') - if not signavio_meta_data: - raise ValidationException( - 'No Signavio "Subprocess reference" specified.', - node=self.node, file_name=self.filename) - return one(signavio_meta_data).get('metaValue') diff --git a/SpiffWorkflow/SpiffWorkflow/specs/AcquireMutex.py b/SpiffWorkflow/SpiffWorkflow/specs/AcquireMutex.py index 226e3b8ba..6c3a3c6eb 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/AcquireMutex.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/AcquireMutex.py @@ -1,20 +1,22 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2007 Samuel Abels # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,MA 02110-1301 USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from ..task import TaskState from .base import TaskSpec diff --git a/SpiffWorkflow/SpiffWorkflow/specs/Cancel.py b/SpiffWorkflow/SpiffWorkflow/specs/Cancel.py index 06643bf74..3313e5271 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/Cancel.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/Cancel.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2007 Samuel Abels # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,6 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + from ..exceptions import WorkflowException from .base import TaskSpec diff --git a/SpiffWorkflow/SpiffWorkflow/specs/CancelTask.py b/SpiffWorkflow/SpiffWorkflow/specs/CancelTask.py index 0c8857112..712ae3dbc 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/CancelTask.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/CancelTask.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2007 Samuel Abels # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,6 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + from .Trigger import Trigger diff --git a/SpiffWorkflow/SpiffWorkflow/specs/Celery.py b/SpiffWorkflow/SpiffWorkflow/specs/Celery.py deleted file mode 100644 index 96e2ecd65..000000000 --- a/SpiffWorkflow/SpiffWorkflow/specs/Celery.py +++ /dev/null @@ -1,261 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2007 Samuel Abels -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 USA -import logging -import json - -from ..task import TaskState -from ..exceptions import WorkflowException -from .base import TaskSpec -from ..operators import valueof, Attrib, PathAttrib -from ..util.deep_merge import DeepMerge - -logger = logging.getLogger('spiff') - - -def _eval_args(args, my_task): - """Parses args and evaluates any Attrib entries""" - results = [] - for arg in args: - if isinstance(arg, Attrib) or isinstance(arg, PathAttrib): - results.append(valueof(my_task, arg)) - else: - results.append(arg) - return results - - -def _eval_kwargs(kwargs, my_task): - """Parses kwargs and evaluates any Attrib entries""" - results = {} - for kwarg, value in list(kwargs.items()): - if isinstance(value, Attrib) or isinstance(value, PathAttrib): - results[kwarg] = valueof(my_task, value) - else: - results[kwarg] = value - return results - - -def Serializable(o, my_task): - """Make sure an object is JSON-serializable - Use this to return errors and other info that does not need to be - deserialized or does not contain important app data. Best for returning - error info and such""" - if isinstance(o, (str, dict, int)): - return o - else: - try: - json.dumps(o) - return o - except Exception: - logger.debug("Got a non-serilizeable object: %s" % o, extra=my_task.log_info()) - return o.__repr__() - - -class Celery(TaskSpec): - - """This class implements a celeryd task that is sent to the celery queue for - completion.""" - - def __init__(self, wf_spec, name, call, call_args=None, result_key=None, - merge_results=False, **kwargs): - """Constructor. - - The args/kwargs arguments support Attrib classes in the parameters for - delayed evaluation of inputs until run-time. Example usage: - task = Celery(wfspec, 'MyTask', 'celery.call', - call_args=['hello', 'world', Attrib('number')], - any_param=Attrib('result')) - - For serialization, the celery task_id is stored in internal_data, - but the celery async call is only stored as an attr of the task (since - it is not always serializable). When deserialized, the async_call attr - is reset in the _start call. - - :type wf_spec: WorkflowSpec - :param wf_spec: A reference to the workflow specification. - :type name: str - :param name: The name of the task spec. - :type call: str - :param call: The name of the celery task that needs to be called. - :type call_args: list - :param call_args: args to pass to celery task. - :type result_key: str - :param result_key: The key to use to store the results of the call in - task.internal_data. If None, then dicts are expanded into - internal_data and values are stored in 'result'. - :type merge_results: bool - :param merge_results: merge the results in instead of overwriting - existing fields. - :type kwargs: dict - :param kwargs: kwargs to pass to celery task. - """ - - try: - from celery.app import default_app - except ImportError: - raise Exception("Unable to import python-celery imports.") - assert wf_spec is not None - assert name is not None - assert call is not None - TaskSpec.__init__(self, wf_spec, name, **kwargs) - self.description = kwargs.pop('description', '') - self.call = call or [] - self.args = call_args or {} - self.merge_results = merge_results - skip = 'data', 'defines', 'pre_assign', 'post_assign' - self.kwargs = dict(i for i in list(kwargs.items()) if i[0] not in skip) - self.result_key = result_key - - def _send_call(self, my_task): - """Sends Celery asynchronous call and stores async call information for - retrieval laster""" - args, kwargs = None, None - if self.args: - args = _eval_args(self.args, my_task) - if self.kwargs: - kwargs = _eval_kwargs(self.kwargs, my_task) - logger.debug(f"{self.name} (task id {my_task.id}) calling {self.call}", extra=my_task.log_info()) - async_call = default_app.send_task(self.call, args=args, kwargs=kwargs) - my_task._set_internal_data(task_id=async_call.task_id) - my_task.async_call = async_call - logger.debug(f"'{self.call}' called: {my_task.async_call.task_id}", extra=my_task.log_info()) - - def _restart(self, my_task): - """ Abort celery task and retry it""" - if not my_task._has_state(TaskState.WAITING): - raise WorkflowException(my_task, "Cannot refire a task that is not" - "in WAITING state") - # Check state of existing call and abort it (save history) - if my_task._get_internal_data('task_id') is not None: - if not hasattr(my_task, 'async_call'): - task_id = my_task._get_internal_data('task_id') - my_task.async_call = default_app.AsyncResult(task_id) - my_task.deserialized = True - my_task.async_call.state # manually refresh - async_call = my_task.async_call - if async_call.state == 'FAILED': - pass - elif async_call.state in ['RETRY', 'PENDING', 'STARTED']: - async_call.revoke() - logger.info( - f"Celery task '{async_call.state}' was in {async_call} state and was revoked", - extra=my_task.log_info() - ) - elif async_call.state == 'SUCCESS': - logger.warning(f"Celery task '{async_call}' succeeded, but a refire was requested", - extra=my_task.log_info() - ) - self._clear_celery_task_data(my_task) - # Retrigger - return self._start(my_task) - - def _clear_celery_task_data(self, my_task): - """ Clear celery task data """ - # Save history - if 'task_id' in my_task.internal_data: - # Save history for diagnostics/forensics - history = my_task._get_internal_data('task_history', []) - history.append(my_task._get_internal_data('task_id')) - del my_task.internal_data['task_id'] - my_task._set_internal_data(task_history=history) - if 'task_state' in my_task.internal_data: - del my_task.internal_data['task_state'] - if 'error' in my_task.internal_data: - del my_task.internal_data['error'] - if hasattr(my_task, 'async_call'): - delattr(my_task, 'async_call') - if hasattr(my_task, 'deserialized'): - delattr(my_task, 'deserialized') - - def _start(self, my_task, force=False): - """Returns False when successfully fired, True otherwise""" - - # Deserialize async call if necessary - if not hasattr(my_task, 'async_call') and \ - my_task._get_internal_data('task_id') is not None: - task_id = my_task._get_internal_data('task_id') - my_task.async_call = default_app.AsyncResult(task_id) - my_task.deserialized = True - logger.debug(f"Reanimate AsyncCall {task_id}", extra=my_task.log_info()) - - # Make the call if not already done - if not hasattr(my_task, 'async_call'): - self._send_call(my_task) - - # Get call status (and manually refresh if deserialized) - if getattr(my_task, "deserialized", False): - my_task.async_call.state # must manually refresh if deserialized - if my_task.async_call.state == 'FAILURE': - logger.debug( - f"Async Call for task '{my_task.get_name()}' failed: {my_task.async_call.info}", - extra=my_task.log_info() - ) - info = {} - info['traceback'] = my_task.async_call.traceback - info['info'] = Serializable(my_task.async_call.info, my_task) - info['state'] = my_task.async_call.state - my_task._set_internal_data(task_state=info) - elif my_task.async_call.state == 'RETRY': - info = {} - info['traceback'] = my_task.async_call.traceback - info['info'] = Serializable(my_task.async_call.info, my_task) - info['state'] = my_task.async_call.state - my_task._set_internal_data(task_state=info) - elif my_task.async_call.ready(): - result = my_task.async_call.result - if isinstance(result, Exception): - logger.warning(f"Celery call {self.call} failed: {result}", extra=my_task.log_info()) - my_task._set_internal_data(error=Serializable(result, my_task)) - return False - logger.debug(f"Completed celery call {self.call} with result={result}", extra=my_task.log_info()) - # Format result - if self.result_key: - data = {self.result_key: result} - else: - if isinstance(result, dict): - data = result - else: - data = {'result': result} - # Load formatted result into internal_data - if self.merge_results: - DeepMerge.merge(my_task.internal_data, data) - else: - my_task.set_data(**data) - return True - else: - logger.debug( - f"async_call.ready()={my_task.async_call.ready()}. TryFire for '{my_task.get_name()}' returning False", - extra=my_task.log_info() - ) - return False - - def _update_hook(self, my_task): - super()._update_hook(my_task) - if not self._start(my_task): - if not my_task._has_state(TaskState.WAITING): - my_task._set_state(TaskState.WAITING) - else: - return True - - def serialize(self, serializer): - return serializer.serialize_celery(self) - - @classmethod - def deserialize(self, serializer, wf_spec, s_state): - spec = serializer.deserialize_celery(wf_spec, s_state) - return spec diff --git a/SpiffWorkflow/SpiffWorkflow/specs/Choose.py b/SpiffWorkflow/SpiffWorkflow/specs/Choose.py index ee30bc05d..71929b9bf 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/Choose.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/Choose.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2007 Samuel Abels # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,6 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + from .base import TaskSpec from .Trigger import Trigger diff --git a/SpiffWorkflow/SpiffWorkflow/specs/ExclusiveChoice.py b/SpiffWorkflow/SpiffWorkflow/specs/ExclusiveChoice.py index 5eaf52a93..2aab12194 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/ExclusiveChoice.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/ExclusiveChoice.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2007 Samuel Abels +# Copyright (C) 2007 Samuel Abels, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,6 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + from ..task import TaskState from ..exceptions import WorkflowException from .MultiChoice import MultiChoice diff --git a/SpiffWorkflow/SpiffWorkflow/specs/Execute.py b/SpiffWorkflow/SpiffWorkflow/specs/Execute.py index 995715ea6..06537abbc 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/Execute.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/Execute.py @@ -1,20 +1,22 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2007 Samuel Abels # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + import subprocess from ..task import TaskState diff --git a/SpiffWorkflow/SpiffWorkflow/specs/Gate.py b/SpiffWorkflow/SpiffWorkflow/specs/Gate.py index 4ef8c976c..c142cccbb 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/Gate.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/Gate.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2007 Samuel Abels # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,6 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + from ..task import TaskState from .base import TaskSpec diff --git a/SpiffWorkflow/SpiffWorkflow/specs/Join.py b/SpiffWorkflow/SpiffWorkflow/specs/Join.py index 63c365622..45796cc9a 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/Join.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/Join.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2007 Samuel Abels +# Copyright (C) 2007 Samuel Abels, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,6 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + from ..task import Task, TaskState from ..exceptions import WorkflowException from .base import TaskSpec diff --git a/SpiffWorkflow/SpiffWorkflow/specs/Merge.py b/SpiffWorkflow/SpiffWorkflow/specs/Merge.py index 1141f712b..6b6aa4fad 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/Merge.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/Merge.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2007 Samuel Abels +# Copyright (C) 2007 Samuel Abels, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. diff --git a/SpiffWorkflow/SpiffWorkflow/specs/MultiChoice.py b/SpiffWorkflow/SpiffWorkflow/specs/MultiChoice.py index dc2aa194a..0856855bc 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/MultiChoice.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/MultiChoice.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2007 Samuel Abels +# Copyright (C) 2007 Samuel Abels, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,6 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + from ..task import TaskState from ..exceptions import WorkflowException from .base import TaskSpec diff --git a/SpiffWorkflow/SpiffWorkflow/specs/MultiInstance.py b/SpiffWorkflow/SpiffWorkflow/specs/MultiInstance.py index 9efea52a4..03313a05a 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/MultiInstance.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/MultiInstance.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2007 Samuel Abels # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,6 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + from ..task import TaskState from .base import TaskSpec from ..operators import valueof @@ -50,7 +51,6 @@ class MultiInstance(TaskSpec): raise ValueError('times argument is required') TaskSpec.__init__(self, wf_spec, name, **kwargs) self.times = times - self.prevtaskclass = None def _find_my_task(self, task): for thetask in task.workflow.task_tree: diff --git a/SpiffWorkflow/SpiffWorkflow/specs/ReleaseMutex.py b/SpiffWorkflow/SpiffWorkflow/specs/ReleaseMutex.py index d2efb2714..c4514f168 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/ReleaseMutex.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/ReleaseMutex.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2007 Samuel Abels # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,6 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + from .base import TaskSpec diff --git a/SpiffWorkflow/SpiffWorkflow/specs/Simple.py b/SpiffWorkflow/SpiffWorkflow/specs/Simple.py index 22d8b528c..504ccea9b 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/Simple.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/Simple.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2007 Samuel Abels # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,6 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + from .base import TaskSpec @@ -29,7 +30,6 @@ class Simple(TaskSpec): If more than one output is connected, the task performs an implicit parallel split. """ - def serialize(self, serializer): return serializer.serialize_simple(self) diff --git a/SpiffWorkflow/SpiffWorkflow/specs/StartTask.py b/SpiffWorkflow/SpiffWorkflow/specs/StartTask.py index 5f99a90a5..578d4df71 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/StartTask.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/StartTask.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2007 Samuel Abels # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. diff --git a/SpiffWorkflow/SpiffWorkflow/specs/SubWorkflow.py b/SpiffWorkflow/SpiffWorkflow/specs/SubWorkflow.py index 177891fae..8b2f1c1a1 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/SubWorkflow.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/SubWorkflow.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2007 Samuel Abels +# Copyright (C) 2007 Samuel Abels, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,6 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + import os from lxml import etree diff --git a/SpiffWorkflow/SpiffWorkflow/specs/ThreadMerge.py b/SpiffWorkflow/SpiffWorkflow/specs/ThreadMerge.py index b522913b0..fc867cab5 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/ThreadMerge.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/ThreadMerge.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2007 Samuel Abels +# Copyright (C) 2007 Samuel Abels, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,6 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + from ..task import TaskState from ..exceptions import WorkflowException from ..operators import valueof diff --git a/SpiffWorkflow/SpiffWorkflow/specs/ThreadSplit.py b/SpiffWorkflow/SpiffWorkflow/specs/ThreadSplit.py index 7b6471ff9..80ee8dcd2 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/ThreadSplit.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/ThreadSplit.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2007 Samuel Abels # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,6 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + from ..task import TaskState from .base import TaskSpec from .ThreadStart import ThreadStart diff --git a/SpiffWorkflow/SpiffWorkflow/specs/ThreadStart.py b/SpiffWorkflow/SpiffWorkflow/specs/ThreadStart.py index 65e27a81e..1522f88df 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/ThreadStart.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/ThreadStart.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2007 Samuel Abels # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,6 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + from .base import TaskSpec from SpiffWorkflow.task import TaskState diff --git a/SpiffWorkflow/SpiffWorkflow/specs/Transform.py b/SpiffWorkflow/SpiffWorkflow/specs/Transform.py index d371c43b2..ccb04f64d 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/Transform.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/Transform.py @@ -1,20 +1,22 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2007 Samuel Abels # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + import logging from .base import TaskSpec @@ -56,7 +58,7 @@ class Transform(TaskSpec): if self.transforms: for transform in self.transforms: - logger.debug(f'Execute transform', extra=my_task.log_info({'transform': transform})) + logger.debug('Execute transform', extra=my_task.log_info({'transform': transform})) exec(transform) return True diff --git a/SpiffWorkflow/SpiffWorkflow/specs/Trigger.py b/SpiffWorkflow/SpiffWorkflow/specs/Trigger.py index c8d5fbfc0..6d4e2445c 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/Trigger.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/Trigger.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - # Copyright (C) 2007 Samuel Abels # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,6 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + from ..task import TaskState from .base import TaskSpec from ..operators import valueof diff --git a/SpiffWorkflow/SpiffWorkflow/specs/WorkflowSpec.py b/SpiffWorkflow/SpiffWorkflow/specs/WorkflowSpec.py index 6519873f8..98e2d8f42 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/WorkflowSpec.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/WorkflowSpec.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2007 Samuel Abels +# Copyright (C) 2007 Samuel Abels, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -45,7 +45,6 @@ class WorkflowSpec(object): if task_spec.name in self.task_specs: raise KeyError('Duplicate task spec name: ' + task_spec.name) self.task_specs[task_spec.name] = task_spec - task_spec.id = self.name + '_' + str(len(self.task_specs)) def get_task_spec_from_name(self, name): """ @@ -58,22 +57,6 @@ class WorkflowSpec(object): """ return self.task_specs.get(name) - def get_task_spec_from_id(self, id): - """ - Returns the task with the given name. - - :type name: str - :param name: The name of the task spec. - :rtype: TaskSpec - :returns: The task spec with the given name. - """ - ret_spec = None - for x in self.task_specs: - if self.task_specs[x].id == id: - ret_spec = self.task_specs[x] - return ret_spec - - def validate(self): """Checks integrity of workflow and reports any problems with it. diff --git a/SpiffWorkflow/SpiffWorkflow/specs/__init__.py b/SpiffWorkflow/SpiffWorkflow/specs/__init__.py index e69de29bb..d50ddaba8 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/__init__.py @@ -0,0 +1,16 @@ +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/specs/base.py b/SpiffWorkflow/SpiffWorkflow/specs/base.py index 7cb637398..25fdd5b15 100644 --- a/SpiffWorkflow/SpiffWorkflow/specs/base.py +++ b/SpiffWorkflow/SpiffWorkflow/specs/base.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2007 Samuel Abels +# Copyright (C) 2007 Samuel Abels, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -16,6 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + from abc import abstractmethod from ..util.event import Event @@ -84,21 +85,15 @@ class TaskSpec(object): :param pre_assign: a list of name/value pairs :type post_assign: list((str, object)) :param post_assign: a list of name/value pairs - :type position: dict((str, object)) - :param position: a dict containing an 'x' and 'y' with coordinates - that describe where the element occurred in the - diagram. """ assert wf_spec is not None assert name is not None self._wf_spec = wf_spec - self.id = None self.name = str(name) - self.description = kwargs.get('description', '') + self.description = kwargs.get('description', None) self.inputs = [] self.outputs = [] self.manual = kwargs.get('manual', False) - self.internal = False # Only for easing debugging. self.data = kwargs.get('data', {}) self.defines = kwargs.get('defines', {}) self.pre_assign = kwargs.get('pre_assign',[]) @@ -115,7 +110,6 @@ class TaskSpec(object): self._wf_spec._add_notify(self) self.data.update(self.defines) - assert self.id is not None @property def spec_type(self): @@ -409,14 +403,12 @@ class TaskSpec(object): class_name = module + '.' + self.__class__.__name__ return { - 'id':self.id, 'class': class_name, 'name':self.name, 'description':self.description, - 'inputs':[x.id for x in self.inputs], - 'outputs':[x.id for x in self.outputs], + 'inputs':[x.name for x in self.inputs], + 'outputs':[x.name for x in self.outputs], 'manual':self.manual, - 'internal':self.internal, 'data':self.data, 'defines':self.defines, 'pre_assign':self.pre_assign, @@ -447,34 +439,14 @@ class TaskSpec(object): :returns: The task specification instance. """ out = cls(wf_spec,s_state.get('name')) - out.id = s_state.get('id') out.name = s_state.get('name') out.description = s_state.get('description') out.inputs = s_state.get('inputs') out.outputs = s_state.get('outputs') out.manual = s_state.get('manual') - out.internal = s_state.get('internal') out.data = s_state.get('data') out.defines = s_state.get('defines') out.pre_assign = s_state.get('pre_assign') out.post_assign = s_state.get('post_assign') out.lookahead = s_state.get('lookahead') return out - - def task_should_set_children_future(self, my_task): - """ - Hook to allow a task_spec to indicate if a task should - set_future_children. - - Subclasses can override to influence this decision. - """ - return my_task.state == TaskState.COMPLETED or my_task.state == TaskState.READY - - def task_will_set_children_future(self, my_task): - """ - Called right before a task runs the logic for set_children_future if - task_should_set_children_future returns True. - - Subclasses can override to perform work during that stage of execution. - """ - pass diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/__init__.py b/SpiffWorkflow/SpiffWorkflow/spiff/__init__.py index e69de29bb..b93acc8d9 100644 --- a/SpiffWorkflow/SpiffWorkflow/spiff/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/spiff/__init__.py @@ -0,0 +1,18 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/event_definition.py b/SpiffWorkflow/SpiffWorkflow/spiff/event_definition.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/parser/__init__.py b/SpiffWorkflow/SpiffWorkflow/spiff/parser/__init__.py index e69de29bb..b93acc8d9 100644 --- a/SpiffWorkflow/SpiffWorkflow/spiff/parser/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/spiff/parser/__init__.py @@ -0,0 +1,18 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/parser/event_parsers.py b/SpiffWorkflow/SpiffWorkflow/spiff/parser/event_parsers.py index 3e19be839..ba8093cb2 100644 --- a/SpiffWorkflow/SpiffWorkflow/spiff/parser/event_parsers.py +++ b/SpiffWorkflow/SpiffWorkflow/spiff/parser/event_parsers.py @@ -1,8 +1,27 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from SpiffWorkflow.bpmn.parser.event_parsers import EventDefinitionParser, ReceiveTaskParser from SpiffWorkflow.bpmn.parser.event_parsers import StartEventParser, EndEventParser, \ IntermediateCatchEventParser, IntermediateThrowEventParser, BoundaryEventParser, \ SendTaskParser -from SpiffWorkflow.spiff.specs.events.event_definitions import MessageEventDefinition +from SpiffWorkflow.spiff.specs.event_definitions import MessageEventDefinition from SpiffWorkflow.bpmn.parser.util import one from SpiffWorkflow.spiff.parser.task_spec import SpiffTaskParser diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/parser/process.py b/SpiffWorkflow/SpiffWorkflow/spiff/parser/process.py index 203078e15..2dc92de5a 100644 --- a/SpiffWorkflow/SpiffWorkflow/spiff/parser/process.py +++ b/SpiffWorkflow/SpiffWorkflow/spiff/parser/process.py @@ -1,20 +1,49 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + import os from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser -from SpiffWorkflow.bpmn.parser.BpmnParser import BpmnValidator, full_tag, ValidationException +from SpiffWorkflow.bpmn.parser.BpmnParser import BpmnValidator, full_tag -from SpiffWorkflow.bpmn.specs.events.EndEvent import EndEvent -from SpiffWorkflow.bpmn.specs.events.StartEvent import StartEvent -from SpiffWorkflow.bpmn.specs.events.IntermediateEvent import IntermediateThrowEvent, BoundaryEvent, IntermediateCatchEvent +from SpiffWorkflow.bpmn.specs.defaults import ( + StartEvent, + EndEvent, + IntermediateCatchEvent, + IntermediateThrowEvent, + BoundaryEvent, +) + +from SpiffWorkflow.spiff.specs.defaults import ( + UserTask, + ManualTask, + NoneTask, + ScriptTask, + SendTask, + ReceiveTask, + BusinessRuleTask, + SubWorkflowTask, + CallActivity, + TransactionSubprocess, + ServiceTask +) -from SpiffWorkflow.spiff.specs.none_task import NoneTask -from SpiffWorkflow.spiff.specs.manual_task import ManualTask -from SpiffWorkflow.spiff.specs.user_task import UserTask -from SpiffWorkflow.spiff.specs.script_task import ScriptTask -from SpiffWorkflow.spiff.specs.subworkflow_task import SubWorkflowTask, TransactionSubprocess, CallActivity -from SpiffWorkflow.spiff.specs.service_task import ServiceTask -from SpiffWorkflow.spiff.specs.events.event_types import SendTask, ReceiveTask -from SpiffWorkflow.spiff.specs.business_rule_task import BusinessRuleTask from SpiffWorkflow.spiff.parser.task_spec import ( SpiffTaskParser, SubWorkflowParser, @@ -23,11 +52,15 @@ from SpiffWorkflow.spiff.parser.task_spec import ( ScriptTaskParser, BusinessRuleTaskParser ) -from SpiffWorkflow.spiff.parser.event_parsers import (SpiffStartEventParser, SpiffEndEventParser, SpiffBoundaryEventParser, - SpiffIntermediateCatchEventParser, SpiffIntermediateThrowEventParser, SpiffSendTaskParser, SpiffReceiveTaskParser) - - -from SpiffWorkflow.spiff.parser.task_spec import BusinessRuleTaskParser +from SpiffWorkflow.spiff.parser.event_parsers import ( + SpiffStartEventParser, + SpiffEndEventParser, + SpiffBoundaryEventParser, + SpiffIntermediateCatchEventParser, + SpiffIntermediateThrowEventParser, + SpiffSendTaskParser, + SpiffReceiveTaskParser +) SPIFF_XSD = os.path.join(os.path.dirname(__file__), 'schema', 'spiffworkflow.xsd') VALIDATOR = BpmnValidator(imports={'spiffworkflow': SPIFF_XSD}) @@ -53,9 +86,3 @@ class SpiffBpmnParser(BpmnDmnParser): full_tag('receiveTask'): (SpiffReceiveTaskParser, ReceiveTask), full_tag('businessRuleTask'): (BusinessRuleTaskParser, BusinessRuleTask) } - - def create_parser(self, node, filename=None, lane=None): - parser = self.PROCESS_PARSER_CLASS(self, node, self.namespaces, self.data_stores, filename=filename, lane=lane) - if parser.get_id() in self.process_parsers: - raise ValidationException(f'Duplicate process ID: {parser.get_id()}', node=node, file_name=filename) - self.process_parsers[parser.get_id()] = parser diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/parser/task_spec.py b/SpiffWorkflow/SpiffWorkflow/spiff/parser/task_spec.py index bf337a92a..8d3400ba9 100644 --- a/SpiffWorkflow/SpiffWorkflow/spiff/parser/task_spec.py +++ b/SpiffWorkflow/SpiffWorkflow/spiff/parser/task_spec.py @@ -1,11 +1,34 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from lxml import etree from SpiffWorkflow.bpmn.parser.TaskParser import TaskParser from SpiffWorkflow.bpmn.parser.task_parsers import SubprocessParser from SpiffWorkflow.bpmn.parser.util import xpath_eval -from SpiffWorkflow.spiff.specs.multiinstance_task import StandardLoopTask, ParallelMultiInstanceTask, SequentialMultiInstanceTask -from SpiffWorkflow.spiff.specs.business_rule_task import BusinessRuleTask +from SpiffWorkflow.spiff.specs.defaults import ( + StandardLoopTask, + ParallelMultiInstanceTask, + SequentialMultiInstanceTask, + BusinessRuleTask +) SPIFFWORKFLOW_MODEL_NS = 'http://spiffworkflow.org/bpmn/schema/1.0/core' SPIFFWORKFLOW_MODEL_PREFIX = 'spiffworkflow' @@ -92,6 +115,8 @@ class SpiffTaskParser(TaskParser): super()._copy_task_attrs(original) self.task.prescript = original.prescript self.task.postscript = original.postscript + original.prescript = None + original.postscript = None def create_task(self): # The main task parser already calls this, and even sets an attribute, but @@ -101,12 +126,7 @@ class SpiffTaskParser(TaskParser): extensions = self.parse_extensions() prescript = extensions.get('preScript') postscript = extensions.get('postScript') - return self.spec_class(self.spec, self.get_task_spec_name(), - lane=self.lane, - description=self.node.get('name', None), - position=self.position, - prescript=prescript, - postscript=postscript) + return self.spec_class(self.spec, self.bpmn_id, prescript=prescript, postscript=postscript, **self.bpmn_attributes) class SubWorkflowParser(SpiffTaskParser): @@ -117,11 +137,12 @@ class SubWorkflowParser(SpiffTaskParser): postscript = extensions.get('postScript') subworkflow_spec = SubprocessParser.get_subprocess_spec(self) return self.spec_class( - self.spec, self.get_task_spec_name(), subworkflow_spec, - lane=self.lane, position=self.position, - description=self.node.get('name', None), + self.spec, + self.bpmn_id, + subworkflow_spec=subworkflow_spec, prescript=prescript, - postscript=postscript) + postscript=postscript, + **self.bpmn_attributes) class ScriptTaskParser(SpiffTaskParser): @@ -130,10 +151,7 @@ class ScriptTaskParser(SpiffTaskParser): for child_node in self.node: if child_node.tag.endswith('script'): script = child_node.text - return self.spec_class( - self.spec, self.get_task_spec_name(), script, - lane=self.lane, position=self.position, - description=self.node.get('name', None)) + return self.spec_class(self.spec, self.bpmn_id, script, **self.bpmn_attributes) class CallActivityParser(SpiffTaskParser): @@ -144,11 +162,12 @@ class CallActivityParser(SpiffTaskParser): postscript = extensions.get('postScript') subworkflow_spec = SubprocessParser.get_call_activity_spec(self) return self.spec_class( - self.spec, self.get_task_spec_name(), subworkflow_spec, - lane=self.lane, position=self.position, - description=self.node.get('name', None), + self.spec, + self.bpmn_id, + subworkflow_spec=subworkflow_spec, prescript=prescript, - postscript=postscript) + postscript=postscript, + **self.bpmn_attributes) class ServiceTaskParser(SpiffTaskParser): def create_task(self): @@ -157,13 +176,14 @@ class ServiceTaskParser(SpiffTaskParser): prescript = extensions.get('preScript') postscript = extensions.get('postScript') return self.spec_class( - self.spec, self.get_task_spec_name(), - operator['name'], operator['parameters'], - operator['resultVariable'], - description=self.node.get('name', None), - lane=self.lane, position=self.position, + self.spec, + self.bpmn_id, + operation_name=operator['name'], + operation_params=operator['parameters'], + result_variable=operator['resultVariable'], prescript=prescript, - postscript=postscript) + postscript=postscript, + **self.bpmn_attributes) class BusinessRuleTaskParser(SpiffTaskParser): @@ -174,13 +194,11 @@ class BusinessRuleTaskParser(SpiffTaskParser): postscript = extensions.get('postScript') return BusinessRuleTask( self.spec, - self.get_task_spec_name(), + self.bpmn_id, dmnEngine=self.process_parser.parser.get_engine(decision_ref, self.node), - lane=self.lane, - position=self.position, - description=self.node.get('name', None), prescript=prescript, postscript=postscript, + **self.bpmn_attributes, ) @staticmethod diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/serializer/__init__.py b/SpiffWorkflow/SpiffWorkflow/spiff/serializer/__init__.py index e69de29bb..b93acc8d9 100644 --- a/SpiffWorkflow/SpiffWorkflow/spiff/serializer/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/spiff/serializer/__init__.py @@ -0,0 +1,18 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/serializer/config.py b/SpiffWorkflow/SpiffWorkflow/spiff/serializer/config.py index 771cb5d27..1db2612a4 100644 --- a/SpiffWorkflow/SpiffWorkflow/spiff/serializer/config.py +++ b/SpiffWorkflow/SpiffWorkflow/spiff/serializer/config.py @@ -1,9 +1,28 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from copy import deepcopy from SpiffWorkflow.bpmn.serializer.workflow import DEFAULT_SPEC_CONFIG from SpiffWorkflow.bpmn.serializer.task_spec import ( - SimpleTaskConverter, - StartTaskConverter, + SimpleBpmnTaskConverter, + BpmnStartTaskConverter, EndJoinConverter, StartEventConverter, EndEventConverter, @@ -15,7 +34,6 @@ from SpiffWorkflow.bpmn.serializer.task_spec import ( ParallelGatewayConverter, ExclusiveGatewayConverter, InclusiveGatewayConverter, - StandardLoopTaskConverter, ) from .task_spec import ( @@ -29,6 +47,7 @@ from .task_spec import ( SubprocessTaskConverter, TransactionSubprocessConverter, CallActivityTaskConverter, + StandardLoopTaskConverter, ParallelMultiInstanceTaskConverter, SequentialMultiInstanceTaskConverter, BusinessRuleTaskConverter, @@ -39,8 +58,8 @@ from .event_definition import MessageEventDefinitionConverter SPIFF_SPEC_CONFIG = deepcopy(DEFAULT_SPEC_CONFIG) SPIFF_SPEC_CONFIG['task_specs'] = [ - SimpleTaskConverter, - StartTaskConverter, + SimpleBpmnTaskConverter, + BpmnStartTaskConverter, EndJoinConverter, StartEventConverter, EndEventConverter, diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/serializer/event_definition.py b/SpiffWorkflow/SpiffWorkflow/spiff/serializer/event_definition.py index 648c97931..990142c72 100644 --- a/SpiffWorkflow/SpiffWorkflow/spiff/serializer/event_definition.py +++ b/SpiffWorkflow/SpiffWorkflow/spiff/serializer/event_definition.py @@ -1,6 +1,25 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from SpiffWorkflow.bpmn.serializer.helpers.spec import EventDefinitionConverter -from SpiffWorkflow.spiff.specs.events.event_definitions import MessageEventDefinition +from SpiffWorkflow.spiff.specs.event_definitions import MessageEventDefinition class MessageEventDefinitionConverter(EventDefinitionConverter): diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/serializer/task_spec.py b/SpiffWorkflow/SpiffWorkflow/spiff/serializer/task_spec.py index b5a4e5899..6cfe38868 100644 --- a/SpiffWorkflow/SpiffWorkflow/spiff/serializer/task_spec.py +++ b/SpiffWorkflow/SpiffWorkflow/spiff/serializer/task_spec.py @@ -1,23 +1,47 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from SpiffWorkflow.bpmn.serializer.helpers.spec import TaskSpecConverter from SpiffWorkflow.bpmn.serializer.task_spec import MultiInstanceTaskConverter from SpiffWorkflow.dmn.serializer.task_spec import BaseBusinessRuleTaskConverter -from SpiffWorkflow.spiff.specs.none_task import NoneTask -from SpiffWorkflow.spiff.specs.manual_task import ManualTask -from SpiffWorkflow.spiff.specs.user_task import UserTask -from SpiffWorkflow.spiff.specs.script_task import ScriptTask -from SpiffWorkflow.spiff.specs.service_task import ServiceTask -from SpiffWorkflow.spiff.specs.subworkflow_task import SubWorkflowTask, TransactionSubprocess, CallActivity -from SpiffWorkflow.spiff.specs.events.event_types import SendTask, ReceiveTask -from SpiffWorkflow.spiff.specs.multiinstance_task import StandardLoopTask, ParallelMultiInstanceTask, SequentialMultiInstanceTask -from SpiffWorkflow.spiff.specs.business_rule_task import BusinessRuleTask - +from SpiffWorkflow.spiff.specs.defaults import ( + NoneTask, + ManualTask, + UserTask, + ScriptTask, + SendTask, + ReceiveTask, + StandardLoopTask, + ParallelMultiInstanceTask, + SequentialMultiInstanceTask, + BusinessRuleTask, + SubWorkflowTask, + CallActivity, + TransactionSubprocess, + ServiceTask +) class SpiffBpmnTaskConverter(TaskSpecConverter): def to_dict(self, spec): dct = self.get_default_attributes(spec) - dct.update(self.get_bpmn_attributes(spec)) dct['prescript'] = spec.prescript dct['postscript'] = spec.postscript return dct @@ -136,7 +160,6 @@ class StandardLoopTaskConverter(SpiffBpmnTaskConverter): def to_dict(self, spec): dct = self.get_default_attributes(spec) - dct.update(self.get_bpmn_attributes(spec)) dct.update(self.get_standard_loop_attributes(spec)) return dct diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/specs/__init__.py b/SpiffWorkflow/SpiffWorkflow/spiff/specs/__init__.py index e69de29bb..b93acc8d9 100644 --- a/SpiffWorkflow/SpiffWorkflow/spiff/specs/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/spiff/specs/__init__.py @@ -0,0 +1,18 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/specs/business_rule_task.py b/SpiffWorkflow/SpiffWorkflow/spiff/specs/business_rule_task.py deleted file mode 100644 index 204712ee3..000000000 --- a/SpiffWorkflow/SpiffWorkflow/spiff/specs/business_rule_task.py +++ /dev/null @@ -1,5 +0,0 @@ -from SpiffWorkflow.spiff.specs.spiff_task import SpiffBpmnTask -from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask as DefaultBusinessRuleTask - -class BusinessRuleTask(DefaultBusinessRuleTask, SpiffBpmnTask): - pass diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/specs/defaults.py b/SpiffWorkflow/SpiffWorkflow/spiff/specs/defaults.py new file mode 100644 index 000000000..5dcf7e196 --- /dev/null +++ b/SpiffWorkflow/SpiffWorkflow/spiff/specs/defaults.py @@ -0,0 +1,85 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +from SpiffWorkflow.bpmn.specs.mixins.user_task import UserTask as UserTaskMixin +from SpiffWorkflow.bpmn.specs.mixins.manual_task import ManualTask as ManualTaskMixin +from SpiffWorkflow.bpmn.specs.mixins.none_task import NoneTask as NoneTaskMixin +from SpiffWorkflow.bpmn.specs.mixins.script_task import ScriptTask as ScriptTaskMixin + +from SpiffWorkflow.bpmn.specs.mixins.events.intermediate_event import SendTask, ReceiveTask + +from SpiffWorkflow.bpmn.specs.mixins.subworkflow_task import( + SubWorkflowTask as SubWorkflowTaskMixin, + CallActivity as CallActivityMixin, + TransactionSubprocess as TransactionSubprocessMixin, +) +from SpiffWorkflow.bpmn.specs.mixins.multiinstance_task import ( + StandardLoopTask as BpmnStandardLoopTask, + ParallelMultiInstanceTask as BpmnParallelMITask, + SequentialMultiInstanceTask as BpmnSequentialMITask, +) + +from SpiffWorkflow.dmn.specs.business_rule_task_mixin import BusinessRuleTaskMixin as DefaultBusinessRuleTask + +from .mixins.service_task import ServiceTask as ServiceTaskMixin + +from .spiff_task import SpiffBpmnTask + + +class UserTask(UserTaskMixin, SpiffBpmnTask): + pass + +class ManualTask(ManualTaskMixin, SpiffBpmnTask): + pass + +class NoneTask(NoneTaskMixin, SpiffBpmnTask): + pass + +class ScriptTask(ScriptTaskMixin, SpiffBpmnTask): + pass + +class SendTask(SendTask, SpiffBpmnTask): + pass + +class ReceiveTask(ReceiveTask, SpiffBpmnTask): + pass + +class StandardLoopTask(BpmnStandardLoopTask, SpiffBpmnTask): + pass + +class ParallelMultiInstanceTask(BpmnParallelMITask, SpiffBpmnTask): + pass + +class SequentialMultiInstanceTask(BpmnSequentialMITask, SpiffBpmnTask): + pass + +class BusinessRuleTask(DefaultBusinessRuleTask, SpiffBpmnTask): + pass + +class SubWorkflowTask(SubWorkflowTaskMixin, SpiffBpmnTask): + pass + +class CallActivity(CallActivityMixin, SpiffBpmnTask): + pass + +class TransactionSubprocess(TransactionSubprocessMixin, SpiffBpmnTask): + pass + +class ServiceTask(ServiceTaskMixin, SpiffBpmnTask): + pass diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/specs/events/event_definitions.py b/SpiffWorkflow/SpiffWorkflow/spiff/specs/event_definitions.py similarity index 54% rename from SpiffWorkflow/SpiffWorkflow/spiff/specs/events/event_definitions.py rename to SpiffWorkflow/SpiffWorkflow/spiff/specs/event_definitions.py index d17e31be8..296af76a3 100644 --- a/SpiffWorkflow/SpiffWorkflow/spiff/specs/events/event_definitions.py +++ b/SpiffWorkflow/SpiffWorkflow/spiff/specs/event_definitions.py @@ -1,10 +1,29 @@ -from SpiffWorkflow.bpmn.specs.events.event_definitions import MessageEventDefinition +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +from SpiffWorkflow.bpmn.specs.event_definitions import MessageEventDefinition class MessageEventDefinition(MessageEventDefinition): - def __init__(self, name, correlation_properties=None, expression=None, message_var=None): + def __init__(self, name, correlation_properties=None, expression=None, message_var=None, **kwargs): - super(MessageEventDefinition, self).__init__(name, correlation_properties) + super(MessageEventDefinition, self).__init__(name, correlation_properties, **kwargs) self.expression = expression self.message_var = message_var self.internal = False diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/specs/events/__init__.py b/SpiffWorkflow/SpiffWorkflow/spiff/specs/events/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/specs/events/event_types.py b/SpiffWorkflow/SpiffWorkflow/spiff/specs/events/event_types.py deleted file mode 100644 index 390285d5f..000000000 --- a/SpiffWorkflow/SpiffWorkflow/spiff/specs/events/event_types.py +++ /dev/null @@ -1,8 +0,0 @@ -from ..spiff_task import SpiffBpmnTask -from SpiffWorkflow.bpmn.specs.events.IntermediateEvent import SendTask, ReceiveTask - -class SendTask(SendTask, SpiffBpmnTask): - pass - -class ReceiveTask(ReceiveTask, SpiffBpmnTask): - pass \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/specs/manual_task.py b/SpiffWorkflow/SpiffWorkflow/spiff/specs/manual_task.py deleted file mode 100644 index 0de3e54e7..000000000 --- a/SpiffWorkflow/SpiffWorkflow/spiff/specs/manual_task.py +++ /dev/null @@ -1,10 +0,0 @@ -from SpiffWorkflow.spiff.specs.spiff_task import SpiffBpmnTask - -class ManualTask(SpiffBpmnTask): - - def is_engine_task(self): - return False - - @property - def spec_type(self): - return 'Manual Task' \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/camunda/specs/events/__init__.py b/SpiffWorkflow/SpiffWorkflow/spiff/specs/mixins/__init__.py similarity index 100% rename from SpiffWorkflow/SpiffWorkflow/camunda/specs/events/__init__.py rename to SpiffWorkflow/SpiffWorkflow/spiff/specs/mixins/__init__.py diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/specs/service_task.py b/SpiffWorkflow/SpiffWorkflow/spiff/specs/mixins/service_task.py similarity index 58% rename from SpiffWorkflow/SpiffWorkflow/spiff/specs/service_task.py rename to SpiffWorkflow/SpiffWorkflow/spiff/specs/mixins/service_task.py index c504e84b9..ffff50f34 100644 --- a/SpiffWorkflow/SpiffWorkflow/spiff/specs/service_task.py +++ b/SpiffWorkflow/SpiffWorkflow/spiff/specs/mixins/service_task.py @@ -1,14 +1,32 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + import json from copy import deepcopy -from SpiffWorkflow.bpmn.specs.ServiceTask import ServiceTask -from SpiffWorkflow.exceptions import WorkflowTaskException -from SpiffWorkflow.spiff.specs.spiff_task import SpiffBpmnTask +from SpiffWorkflow.bpmn.specs.mixins.service_task import ServiceTask +from SpiffWorkflow.bpmn.exceptions import WorkflowTaskException -class ServiceTask(SpiffBpmnTask, ServiceTask): +class ServiceTask(ServiceTask): def __init__(self, wf_spec, name, operation_name, operation_params, result_variable, **kwargs): - SpiffBpmnTask.__init__(self, wf_spec, name, **kwargs) + super().__init__(wf_spec, name, **kwargs) self.operation_name = operation_name self.operation_params = operation_params self.result_variable = result_variable diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/specs/multiinstance_task.py b/SpiffWorkflow/SpiffWorkflow/spiff/specs/multiinstance_task.py deleted file mode 100644 index 1d226d587..000000000 --- a/SpiffWorkflow/SpiffWorkflow/spiff/specs/multiinstance_task.py +++ /dev/null @@ -1,15 +0,0 @@ -from SpiffWorkflow.bpmn.specs.MultiInstanceTask import ( - StandardLoopTask as BpmnStandardLoopTask, - ParallelMultiInstanceTask as BpmnParallelMITask, - SequentialMultiInstanceTask as BpmnSequentialMITask, -) -from .spiff_task import SpiffBpmnTask - -class StandardLoopTask(BpmnStandardLoopTask, SpiffBpmnTask): - pass - -class ParallelMultiInstanceTask(BpmnParallelMITask, SpiffBpmnTask): - pass - -class SequentialMultiInstanceTask(BpmnSequentialMITask, SpiffBpmnTask): - pass \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/specs/none_task.py b/SpiffWorkflow/SpiffWorkflow/spiff/specs/none_task.py deleted file mode 100644 index d56758067..000000000 --- a/SpiffWorkflow/SpiffWorkflow/spiff/specs/none_task.py +++ /dev/null @@ -1,10 +0,0 @@ -from SpiffWorkflow.spiff.specs.spiff_task import SpiffBpmnTask - -class NoneTask(SpiffBpmnTask): - - def is_engine_task(self): - return False - - @property - def spec_type(self): - return 'Task' \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/specs/script_task.py b/SpiffWorkflow/SpiffWorkflow/spiff/specs/script_task.py deleted file mode 100644 index c56e7e8c4..000000000 --- a/SpiffWorkflow/SpiffWorkflow/spiff/specs/script_task.py +++ /dev/null @@ -1,6 +0,0 @@ -from SpiffWorkflow.spiff.specs.spiff_task import SpiffBpmnTask -from SpiffWorkflow.bpmn.specs.ScriptTask import ScriptTask as BpmnScriptTask - - -class ScriptTask(BpmnScriptTask, SpiffBpmnTask): - pass diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/specs/spiff_task.py b/SpiffWorkflow/SpiffWorkflow/spiff/specs/spiff_task.py index b5d02e96c..71e657d0c 100644 --- a/SpiffWorkflow/SpiffWorkflow/spiff/specs/spiff_task.py +++ b/SpiffWorkflow/SpiffWorkflow/spiff/specs/spiff_task.py @@ -1,16 +1,33 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from copy import deepcopy from SpiffWorkflow.exceptions import SpiffWorkflowException from SpiffWorkflow.task import TaskState -from SpiffWorkflow.bpmn.specs .BpmnSpecMixin import BpmnSpecMixin +from SpiffWorkflow.bpmn.specs.mixins.bpmn_spec_mixin import BpmnSpecMixin + class SpiffBpmnTask(BpmnSpecMixin): def __init__(self, wf_spec, name, prescript=None, postscript=None, **kwargs): - - # WHy am I doing this instead of just calling super? - # Because I need to deal with multiple inheritance and the kwargs nightmare created by our parser design - BpmnSpecMixin.__init__(self, wf_spec, name, **kwargs) + super().__init__(wf_spec, name, **kwargs) self.prescript = prescript self.postscript = postscript diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/specs/subworkflow_task.py b/SpiffWorkflow/SpiffWorkflow/spiff/specs/subworkflow_task.py deleted file mode 100644 index fb43d8b5d..000000000 --- a/SpiffWorkflow/SpiffWorkflow/spiff/specs/subworkflow_task.py +++ /dev/null @@ -1,68 +0,0 @@ -from SpiffWorkflow.task import TaskState -from SpiffWorkflow.bpmn.specs.SubWorkflowTask import ( - SubWorkflowTask as DefaultSubWorkflow, - TransactionSubprocess as DefaultTransaction, - CallActivity as DefaultCallActivity, -) -from SpiffWorkflow.spiff.specs.spiff_task import SpiffBpmnTask - - -class SubWorkflowTask(DefaultSubWorkflow, SpiffBpmnTask): - - def __init__(self, wf_spec, name, subworkflow_spec, transaction=False, **kwargs): - - SpiffBpmnTask.__init__(self, wf_spec, name, **kwargs) - # We don't so much as a class hierachy as a class pile and I'm giving up doing - # this properly - self.spec = subworkflow_spec - self.transaction = transaction - self.in_assign = [] - self.out_assign = [] - - def _update_hook(self, my_task): - # Don't really like duplicating this, but we need to run SpiffBpmn update rather than the default - wf = my_task.workflow._get_outermost_workflow(my_task) - subprocess = wf.subprocesses.get(my_task.id) - if subprocess is None: - super()._update_hook(my_task) - self.create_workflow(my_task) - self.start_workflow(my_task) - my_task._set_state(TaskState.WAITING) - else: - return subprocess.is_completed() - - def _on_complete_hook(self, my_task): - SpiffBpmnTask._on_complete_hook(self, my_task) - - @property - def spec_type(self): - return 'Subprocess' - - -class TransactionSubprocess(SubWorkflowTask, DefaultTransaction): - - def __init__(self, wf_spec, name, subworkflow_spec, transaction=True, **kwargs): - - SpiffBpmnTask.__init__(self, wf_spec, name, **kwargs) - self.spec = subworkflow_spec - self.transaction = transaction - self.in_assign = [] - self.out_assign = [] - - @property - def spec_type(self): - return 'Transactional Subprocess' - - -class CallActivity(SubWorkflowTask, DefaultCallActivity): - - def __init__(self, wf_spec, name, subworkflow_spec, **kwargs): - - SpiffBpmnTask.__init__(self, wf_spec, name, **kwargs) - self.spec = subworkflow_spec - self.in_assign = [] - self.out_assign = [] - - @property - def spec_type(self): - return 'Call Activity' diff --git a/SpiffWorkflow/SpiffWorkflow/spiff/specs/user_task.py b/SpiffWorkflow/SpiffWorkflow/spiff/specs/user_task.py deleted file mode 100644 index e2b9edb95..000000000 --- a/SpiffWorkflow/SpiffWorkflow/spiff/specs/user_task.py +++ /dev/null @@ -1,10 +0,0 @@ -from SpiffWorkflow.spiff.specs.spiff_task import SpiffBpmnTask - -class UserTask(SpiffBpmnTask): - - def is_engine_task(self): - return False - - @property - def spec_type(self): - return 'User Task' \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/task.py b/SpiffWorkflow/SpiffWorkflow/task.py index 15c9bfac1..58c1d02a6 100644 --- a/SpiffWorkflow/SpiffWorkflow/task.py +++ b/SpiffWorkflow/SpiffWorkflow/task.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2007 Samuel Abels +# Copyright (C) 2007 Samuel Abels, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -17,10 +17,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -import copy +from copy import deepcopy + import logging import time -import warnings from uuid import uuid4 from .util.deep_merge import DeepMerge @@ -103,24 +103,7 @@ TaskStateMasks = { } -class DeprecatedMetaTask(type): - """ - Handle deprecated methods that are now moved to TaskState - """ - TaskNames = {**TaskStateNames, **TaskStateMasks} - TaskStateFromNames = {v: k for k, v in TaskNames.items()} - - def __getattribute__(self, item): - if item in DeprecatedMetaTask.TaskNames.values(): - warnings.warn(f'Task.{item} is deprecated. ' - f'Please use TaskState.{item}', - DeprecationWarning, stacklevel=2) - return DeprecatedMetaTask.TaskStateFromNames[item] - else: - return type.__getattribute__(self, item) - - -class Task(object, metaclass=DeprecatedMetaTask): +class Task(object): """ Used internally for composing a tree that represents the path that is taken (or predicted) within the workflow. @@ -345,42 +328,20 @@ class Task(object, metaclass=DeprecatedMetaTask): def _is_definite(self): return self._has_state(TaskState.DEFINITE_MASK) - def set_children_future(self): + def reset_token(self, data): """ - for a parallel gateway, we need to set up our - children so that the gateway figures out that it needs to join up - the inputs - otherwise our child process never gets marked as - 'READY' - """ - if not self.task_spec.task_should_set_children_future(self): - return - - self.task_spec.task_will_set_children_future(self) - - # now we set this one to execute - self._set_state(TaskState.MAYBE) - self._sync_children(self.task_spec.outputs) - for child in self.children: - child.set_children_future() - - def reset_token(self, data, reset_data=False): - """ - Resets the token to this task. This should allow a trip 'back in time' - as it were to items that have already been completed. - :type reset_data: bool - :param reset_data: Do we want to have the data be where we left of in - this task or not + Reset the workflow to this task, + :param data: set the task data (if None, inherit from parent task) """ self.internal_data = {} - if not reset_data and self.workflow.last_task and self.workflow.last_task.data: - # This is a little sly, the data that will get inherited should - # be from the last completed task, but we don't want to alter - # the tree, so we just set the parent's data to the given data. - self.parent.data = copy.deepcopy(data) - self.workflow.last_task = self.parent - self.set_children_future() # this method actually fixes the problem + if data is None: + self.data = deepcopy(self.parent.data) + descendants = [t for t in self] + self._drop_children(force=True) self._set_state(TaskState.FUTURE) + self.task_spec._predict(self, mask=TaskState.PREDICTED_MASK|TaskState.FUTURE) self.task_spec._update(self) + return descendants[1:] if len(descendants) > 1 else [] def _add_child(self, task_spec, state=TaskState.MAYBE): """ @@ -619,7 +580,7 @@ class Task(object, metaclass=DeprecatedMetaTask): """ Execute the task. - If the return value of task_spec._run is None, assume the task is not finished, + If the return value of task_spec._run is None, assume the task is not finished, and move the task to WAITING. :rtype: boolean or None @@ -636,10 +597,10 @@ class Task(object, metaclass=DeprecatedMetaTask): # This state is intended to indicate a task that is not finished, but will continue # in the background without blocking other unrelated tasks (ie on other branches). # It is a distinct state from "waiting" so that `update` does not have to distinguish - # between tasks that can be started and tasks that have already been started. + # between tasks that can be started and tasks that have already been started. # Spiff can manage deciding if a task can run, but if a task is set to "started", it will - # have to be tracked independently of the workflow and completed manually when it finishes - # for the time being (probably I'll add polling methods in the future, but I'm not exactly + # have to be tracked independently of the workflow and completed manually when it finishes + # for the time being (probably I'll add polling methods in the future, but I'm not exactly # sure how they should work). # I'm adding this state now because I'm adding an error state (which I think there is a # need for) and don't want to go through the hassle of updating serialization of task states diff --git a/SpiffWorkflow/SpiffWorkflow/util/__init__.py b/SpiffWorkflow/SpiffWorkflow/util/__init__.py index e69de29bb..d50ddaba8 100644 --- a/SpiffWorkflow/SpiffWorkflow/util/__init__.py +++ b/SpiffWorkflow/SpiffWorkflow/util/__init__.py @@ -0,0 +1,16 @@ +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA \ No newline at end of file diff --git a/SpiffWorkflow/SpiffWorkflow/util/compat.py b/SpiffWorkflow/SpiffWorkflow/util/compat.py index 8a41fc5d9..fde78864c 100644 --- a/SpiffWorkflow/SpiffWorkflow/util/compat.py +++ b/SpiffWorkflow/SpiffWorkflow/util/compat.py @@ -1,3 +1,20 @@ +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from threading import Lock class mutex(object): diff --git a/SpiffWorkflow/SpiffWorkflow/util/deep_merge.py b/SpiffWorkflow/SpiffWorkflow/util/deep_merge.py index e44905548..8ca25daf9 100644 --- a/SpiffWorkflow/SpiffWorkflow/util/deep_merge.py +++ b/SpiffWorkflow/SpiffWorkflow/util/deep_merge.py @@ -1,3 +1,22 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + class DeepMerge(object): # Merges two deeply nested json-like dictionaries, # useful for updating things like task data. @@ -12,7 +31,8 @@ class DeepMerge(object): @staticmethod def merge(a, b, path=None): "merges b into a" - if path is None: path = [] + if path is None: + path = [] for key in b: if key in a: if a[key] == b[key]: diff --git a/SpiffWorkflow/SpiffWorkflow/util/event.py b/SpiffWorkflow/SpiffWorkflow/util/event.py index ae71d595c..501370525 100644 --- a/SpiffWorkflow/SpiffWorkflow/util/event.py +++ b/SpiffWorkflow/SpiffWorkflow/util/event.py @@ -1,26 +1,27 @@ -# -*- coding: utf-8 -*- - -from builtins import object +# Copyright (C) 2007-2010 Samuel Abels. +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA # # DO NOT EDIT THIS FILE. # THIS CODE IS TAKE FROM Exscript.util: # https://github.com/knipknap/exscript/tree/master/src/Exscript/util # -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ A simple signal/event mechanism. """ diff --git a/SpiffWorkflow/SpiffWorkflow/util/impl.py b/SpiffWorkflow/SpiffWorkflow/util/impl.py index a3ba63606..aa3119b8d 100644 --- a/SpiffWorkflow/SpiffWorkflow/util/impl.py +++ b/SpiffWorkflow/SpiffWorkflow/util/impl.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- - -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -14,6 +14,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA + import sys diff --git a/SpiffWorkflow/SpiffWorkflow/util/levenshtein.py b/SpiffWorkflow/SpiffWorkflow/util/levenshtein.py index cef5f9320..79ec7f9cb 100644 --- a/SpiffWorkflow/SpiffWorkflow/util/levenshtein.py +++ b/SpiffWorkflow/SpiffWorkflow/util/levenshtein.py @@ -1,3 +1,20 @@ +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + from difflib import ndiff diff --git a/SpiffWorkflow/SpiffWorkflow/util/weakmethod.py b/SpiffWorkflow/SpiffWorkflow/util/weakmethod.py index 259025b20..5e8dbab5f 100644 --- a/SpiffWorkflow/SpiffWorkflow/util/weakmethod.py +++ b/SpiffWorkflow/SpiffWorkflow/util/weakmethod.py @@ -1,26 +1,28 @@ -# -*- coding: utf-8 -*- +# Copyright (C) 2007-2010 Samuel Abels. +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA -from builtins import object # # DO NOT EDIT THIS FILE. # THIS CODE IS TAKE FROM Exscript.util: # https://github.com/knipknap/exscript/tree/master/src/Exscript/util # -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ Weak references to bound and unbound methods. """ diff --git a/SpiffWorkflow/SpiffWorkflow/workflow.py b/SpiffWorkflow/SpiffWorkflow/workflow.py index caa5f44be..66f98fd00 100644 --- a/SpiffWorkflow/SpiffWorkflow/workflow.py +++ b/SpiffWorkflow/SpiffWorkflow/workflow.py @@ -1,13 +1,13 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2007 Samuel Abels +# Copyright (C) 2007 Samuel Abels, 2023 Sartography # -# This library is free software; you can redistribute it and/or +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 3.0 of the License, or (at your option) any later version. # -# This library is distributed in the hope that it will be useful, +# SpiffWorkflow is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. @@ -98,7 +98,7 @@ class Workflow(object): mask = TaskState.NOT_FINISHED_MASK iter = Task.Iterator(self.task_tree, mask) try: - nexttask = next(iter) + next(iter) except StopIteration: # No waiting tasks found. return True @@ -172,17 +172,16 @@ class Workflow(object): Cancels all open tasks in the workflow. :type success: bool - :param success: Whether the Workflow should be marked as successfully - completed. + :param success: Whether the Workflow should be marked as successfully completed. """ self.success = success cancel = [] - mask = TaskState.NOT_FINISHED_MASK - for task in Task.Iterator(self.task_tree, mask): + for task in Task.Iterator(self.task_tree, TaskState.NOT_FINISHED_MASK): cancel.append(task) for task in cancel: task.cancel() logger.info(f'Cancel with {len(cancel)} remaining', extra=self.log_info()) + return cancel def get_task_spec_from_name(self, name): """ @@ -259,19 +258,14 @@ class Workflow(object): task = self.get_task_from_id(task_id) return task.run() - def reset_task_from_id(self, task_id): + def reset_from_task_id(self, task_id, data=None): """ Runs the task with the given id. :type task_id: integer :param task_id: The id of the Task object. + :param data: optionall set the task data """ - # Given that this is a BPMN thing it's questionable whether this belongs here at all - # However, since it calls a BPMN thing on `task`, I guess I'll leave it here - # At least it's not in both places any more - data = {} - if self.last_task and self.last_task.data: - data = self.last_task.data task = self.get_task_from_id(task_id) return task.reset_token(data) diff --git a/SpiffWorkflow/doc/bpmn/advanced.rst b/SpiffWorkflow/doc/bpmn/advanced.rst index 60848ef52..13cdc8362 100644 --- a/SpiffWorkflow/doc/bpmn/advanced.rst +++ b/SpiffWorkflow/doc/bpmn/advanced.rst @@ -1,48 +1,91 @@ A More In-Depth Look at Some of SpiffWorkflow's Features ======================================================== +BPMN Task Specs +--------------- + +BPMN Tasks inherit quite a few attributes from :code:`SpiffWorkflow.specs.base.TaskSpec`, but only a few are used. + +* `name`: the unique id of the TaskSpec, and it will correspond to the BPMN ID if that is present +* `description`: we use this attribute to provide a description of the BPMN type (the text that appears here can be overridden in the parser) +* `inputs`: a list of TaskSpec `names` that are parents of this TaskSpec +* `outputs`: a list of TaskSpec `names` that are children of this TaskSpec +* `manual`: :code:`True` if human input is required to complete tasks associated with this TaskSpec + +BPMN Tasks have the following additional attributes. + +* `bpmn_id`: the ID of the BPMN Task (this will be :code:`None` if the task is not visible on the diagram) +* `bpmn_name`: the BPMN name of the Task +* `lane`: the lane of the BPMN Task +* `documentation`: the contents of the BPMN `documentation` element for the Task +* `data_input_associations`: a list of incoming data object references +* `data_output_associtions`: a list of outgoing data object references +* `io_specification`: the BPMN IO specification of the Task + Filtering Tasks --------------- -In our earlier example, all we did was check the lane a task was in and display -it along with the task name and state. +Tasks by Lane +^^^^^^^^^^^^^ -Let's take a look at a sample workflow with lanes: - -.. figure:: figures/lanes.png - :scale: 30% - :align: center - - Workflow with lanes - -To get all the tasks that are ready for the 'Customer' workflow, we could -specify the lane when retrieving ready user tasks: +The :code:`workflow.get_ready_user_tasks` method optionally takes the argument `lane`, which can be used to +restrict the tasks returned to only tasks in that lane. .. code:: python ready_tasks = workflow.get_ready_user_tasks(lane='Customer') -If there were no tasks ready for the 'Customer' lane, you would get an empty list, -and of course if you had no lane that was labeled 'Customer' you would *always* get an -empty list. +will return only tasks in the 'Customer' lane in our example workflow. -We can also get a list of tasks by state. +Tasks by Spec Name +^^^^^^^^^^^^^^^^^^ -We need to import the :code:`Task` object (unless you want to memorize which numbers -correspond to which states). +To retrieve a list of tasks associated with a particular task spec, use :code:`workflow.get_tasks_from_spec_name` + +.. code:: python + + tasks = workflow.get_tasks_from_spec_name('customize_product') + +will return a list containing the Call Actitivities for the customization of a product in our example workflow. + +.. note:: + + The `name` paramter here refers to the task spec name, not the BPMN name (for visible tasks, this will + be the same as the `bpmn_id`) + +Tasks by State +^^^^^^^^^^^^^^ + +We need to import the :code:`TaskState` object (unless you want to memorize which numbers correspond to which states). .. code:: python from SpiffWorkflow.task import TaskState + tasks = workflow.get_tasks(TaskState.COMPLETED) -To get a list of completed tasks +will return a list of completed tasks. + +See :doc:`../concepts` for more information about task states. + +Tasks in a Subprocess or Call Activity +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The :code:`BpmnWorkflow` class maintains a dictionary of subprocesses (the key is the `id` of the Call Activity or +Subprocess Task). :code:`workflow.get_tasks` will start at the top level workflow and recurse through the subprocesses +to create a list of all tasks. It is also possible to start from a particular subprocess: .. code:: python - tasks = workflow.get_tasks(TaskState.COMPLETED) + tasks = workflow.get_tasks_from_spec_name('customize_product') + subprocess = workflow.get_subprocess(tasks[0]) + subprocess_tasks = workflow.get_tasks(workflow=subprocess) -The tasks themselves are not particularly intuitive to work with. So SpiffWorkflow -provides some facilities for obtaining a more user-friendly version of upcoming tasks. +will limit the list of returned tasks to only those in the first product customization. + +.. note:: + + Each :code:`Task` object has a reference to its workflow; so with a Task inside a subprocess, we can call + :code:`workflow.get_tasks(workflow=task.workflow)` to start from our current workflow. Logging ------- @@ -50,7 +93,7 @@ Logging Spiff provides several loggers: - the :code:`spiff` logger, which emits messages when a workflow is initialized and when tasks change state - the :code:`spiff.metrics` logger, which emits messages containing the elapsed duration of tasks - - the :code:`spiff.data` logger, which emits a message when task or workflow data is updated. + - the :code:`spiff.data` logger, which emits a message when :code:`task.update_data` is called or workflow data is retrieved or set. Log level :code:`INFO` will provide reasonably detailed information about state changes. @@ -62,203 +105,164 @@ we define a custom log level .. code:: python - logging.addLevelName(15, 'DATA_LOG') + logging.addLevelName(15, 'DATA') so that we can see the task data in the logs without fully enabling debugging. -The workflow runners take an `-l` argument that can be used to specify the logging level used -when running the example workflows. +The workflow runners take an `-l` argument that can be used to specify the logging level used when running the example workflows. + +We'll write the logs to a file called `data.log` instead of the console to avoid printing very long messages during the workflow. + +Our logging configuration code can be found in `runner/shared.py`. Most of the code is about logging +configuration in Python rather than anything specific to SpiffWorkflow, so we won't go over it in depth. + +Parsing +------- + +Each of the BPMN pacakges (:code:`bpmn`, :code:`spiff`, or :code:`camunda`) has a parser that is preconfigured with +the specs in that package (if a particular TaskSpec is not implemented in the package, :code:`bpmn` TaskSpec is used). + +See the example in :doc:`synthesis` for the basics of creating a parser. The parser can optionally be initialized with + +- a set of namespaces (useful if you have custom extensions) +- a BPMN Validator (the one in the :code:`bpmn` package validates against the BPMN 2.0 spec) +- a mapping of XML tag to Task Spec Descriptions. The default set of descriptions can be found in + :code:`SpiffWorkflow.bpmn.parser.spec_descriptions`. These values will be added to the Task Spec in the `description` attribute + and are intended as a user-friendly description of what the task is. + +The :code:`BpmnValidator` can be used and extended independently of the parser as well; call :code:`validate` with +an :code:`lxml` parsed tree. + +Loading BPMN Files +^^^^^^^^^^^^^^^^^^ + +In addition to :code:`load_bpmn_file`, there are similar functions :code:`load_bpmn_str` which can load the XML from a string, and +:code:`load_bpmn_io`, which can load XML from any object implementing the IO interface, and :code:`add_bpmn_xml`, which can load +BPMN specs from an :code:`lxml` parsed tree. + +Dependencies +^^^^^^^^^^^^ + +The following methods are available for discovering the names of processes and DMN files that may be defined externally: + +- :code:`get_subprocess_specs`: Returns a mapping of name -> :code:`BpmnWorkflowSpec` for any Call Activities referenced by the + provided spec (searches recursively) +- :code:`find_all_spec`: Returns a mapping of name -> :code:`BpmnWorkflowSpec` for all processes used in all files that have been + provided to the parser at that point. +- :code:`get_process_dependences`: Returns a list of process IDs referenced by the provided process ID +- :code:`get_dmn_dependencies`: Returns a list of DMN IDs referenced by the provided process ID + Serialization ------------- -.. warning:: +The :code:`BpmnWorkflowSerializer` has two components - Serialization Changed in Version 1.1.7. - Support for pre-1.1.7 serialization will be dropped in a future release. - The old serialization method still works, but it is deprecated. - To migrate your system to the new version, see "Migrating between - serialization versions" below. +* the `workflow_spec_converter` (which handles serialization of objects that SpiffWorkflow knows about) +* the `data_converter` (which handles serialization of custom objects) -So far, we've only considered the context where we will run the workflow from beginning to end in one -setting. This may not always be the case, we may be executing the workflow in the context of a web server where we -may have a user request a web page where we open a specific workflow that we may be in the middle of, do one step of -that workflow and then the user may be back in a few minutes, or maybe a few hours depending on the application. +Unless you have overriden any of TaskSpecs with custom specs, you should be able to use the serializer +configuration from the package you are importing the parser from (:code:`bpmn`, :code:`spiff`, or :code:`camunda`). +See :doc:`synthesis` for an example. -The :code:`BpmnWorkflowSerializer` class contains a serializer for a workflow containing only standard BPMN Tasks. -Since we are using custom task classes (the Camunda :code:`UserTask` and the DMN :code:`BusinessRuleTask`), -we'll need to supply serializers for those task specs as well. +Serializing Custom Objects +^^^^^^^^^^^^^^^^^^^^^^^^^^ -Strictly speaking, these are not serializers per se: they actually convert the tasks into dictionaries of -JSON-serializable objects. Conversion to JSON is done only as the last step and could easily be replaced with some -other output format. +In `Custom Script Engines`_ , we add some custom methods and objects to our scripting environment. We create a simple +class (a :code:`namedtuple`) that holds the product information for each product. -We'll need to configure a Workflow Spec Converter with our custom classes, as well as an optional -custom data converter. +We'd like to be able to save and restore our custom object. .. code:: python - def create_serializer(task_types, data_converter=None): + ProductInfo = namedtuple('ProductInfo', ['color', 'size', 'style', 'price']) - wf_spec_converter = BpmnWorkflowSerializer.configure_workflow_spec_converter(task_types) - return BpmnWorkflowSerializer(wf_spec_converter, data_converter) + def product_info_to_dict(obj): + return { + 'color': obj.color, + 'size': obj.size, + 'style': obj.style, + 'price': obj.price, + } -We'll call this from our main script: + def product_info_from_dict(dct): + return ProductInfo(**dct) -.. code:: python + registry = DictionaryConverter() + registry.register(ProductInfo, product_info_to_dict, product_info_from_dict) - serializer = create_serializer([ UserTaskConverter, BusinessRuleTaskConverter ], custom_data_converter) +Here we define two functions, one for turning our object into a dictionary of serializable objects, and one for recreating +the object from the dictionary representation we created. -We first configure a workflow spec converter that uses our custom task converters, and then we create -a :code:`BpmnWorkflowSerializer` from our workflow spec and data converters. +We initialize a :code:`DictionaryConverter` and `register` the class and methods. -We'll give the user the option of dumping the workflow at any time. +Registering an object sets up relationships between the class and the serialization and deserialization methods. We go +over how this works in a little more detail in `Custom Serialization in More Depth`_. -.. code:: python +It is also possible to bypass using a :code:`DictionaryConverter` at all for the data serialization process (but not for +the spec serialization process). The only requirement for the the `data_converter` is that it implement the methods - filename = input('Enter filename: ') - state = serializer.serialize_json(workflow) - with open(filename, 'w') as dump: - dump.write(state) +- `convert`, which takes an object and returns something JSON-serializable +- `restore`, which takes a serialized version and returns an object -We'll ask them for a filename and use the serializer to dump the state to that file. +Serialization Versions +^^^^^^^^^^^^^^^^^^^^^^ -To restore the workflow: +As we make changes to Spiff, we may change the serialization format. For example, in 1.2.1, we changed +how subprocesses were handled interally in BPMN workflows and updated how they are serialized and we upraded the +serializer version to 1.1. -.. code:: python +As we release SpiffWorkflow 2.0, there are several more substantial changes, and we'll upgrade the serializer version to 1.2. - if args.restore is not None: - with open(args.restore) as state: - wf = serializer.deserialize_json(state.read()) +Since workflows can contain arbitrary data, and even SpiffWorkflow's internal classes are designed to be customized in ways +that might require special serialization and deserialization, it is possible to override the default version number, to +provide users with a way of tracking their own changes. This can be accomplished by setting the `VERSION` attribute on +the :code:`BpmnWorkflowSerializer` class. -The workflow serializer is designed to be flexible and modular, and as such is a little complicated. It has -two components: - -- a workflow spec converter (which handles workflow and task specs) -- a data converter (which handles workflow and task data). - -The default workflow spec converter likely to meet your needs, either on its own, or with the inclusion of -:code:`UserTask` and :code:`BusinessRuleTask` in the :code:`camnuda` or :code:`spiff` and :code:`dmn` subpackages -of this library, and all you'll need to do is add them to the list of task converters, as we did above. - -However, the default data converter is very simple, adding only JSON-serializable conversions of :code:`datetime` -and :code:`timedelta` objects (we make these available in our default script engine) and UUIDs. If your -workflow or task data contains objects that are not JSON-serializable, you'll need to extend ours, or extend -its base class to create one of your own. - -To extend ours: - -1. Subclass the base data converter -2. Register classes along with functions for converting them to and from dictionaries - -.. code:: python - - from SpiffWorkflow.bpmn.serializer.dictionary import DictionaryConverter - - class MyDataConverter(DictionaryConverter): - - def __init__(self): - super().__init__() - self.register(MyClass, self.my_class_to_dict, self.my_class_from_dict) - - def my_class_to_dict(self, obj): - return obj.__dict__ - - def my_class_from_dict(self, dct): - return MyClass(**dct) - -More information can be found in the class documentation for the -`default converter `_ -and its `base class `_ -. - -You can also replace ours entirely with one of your own. If you do so, you'll need to implement `convert` and -`restore` methods. The former should return a JSON-serializable representation of your workflow data; the -latter should recreate your data from the serialization. - -If you have written any custom task specs, you'll need to implement task spec converters for those as well. - -Task Spec converters are also based on the :code:`DictionaryConverter`. You should be able to use the -`BpmnTaskSpecConverter `_ -as a basis for your custom specs. It provides some methods for extracting attributes from Spiff base classes as well as -standard BPNN attributes from tasks that inherit from :code:`BMPNSpecMixin`. - -The `Camunda User Task Converter `_ -should provide a simple example of how you might create such a converter. - -Migrating Between Serialization Versions ----------------------------------------- - -Old (Non-Versioned) Serializer -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Prior to Spiff 1.1.7, the serialized output did not contain a version number. - -.. code:: python - - old_serializer = BpmnSerializer() # the deprecated serializer. - # new serializer, which can be customized as described above. - serializer = BpmnWorkflowSerializer(version="MY_APP_V_1.0") - -The new serializer has a :code:`get_version` method that will read the version -back out of the serialized json. If the version isn't found, it will return -:code:`None`, and you can then assume it is using the old style serializer. - -.. code:: python - - version = serializer.get_version(some_json) - if version == "MY_APP_V_1.0": - workflow = serializer.deserialize_json(some_json) - else: - workflow = old_serializer.deserialize_workflow(some_json, workflow_spec=spec) - - -If you are not using any custom tasks and do not require custom serialization, then you'll be able to -serialize the workflow in the new format: - -.. code:: python - - new_json = serializer.serialize_json(workflow) - -However, if you use custom tasks or data serialization, you'll also need to specify workflow spec or data -serializers, as in the examples in the previous section, before you'll be able to serialize with the new serializer. -The code would then look more like this: - -.. code:: python - - from SpiffWorkflow.camunda.serializer import UserTaskConverter - - old_serializer = BpmnSerializer() # the deprecated serializer. - - # new serializer, with customizations - wf_spec_converter = BpmnWorkflowSerializer.configure_workflow_spec_converter([UserTaskConverter]) - data_converter = MyDataConverter - serializer = BpmnWorkflowSerializer(wf_spec_converter, data_converter, version="MY_APP_V_1.0") - - version = serializer.get_version(some_json) - if version == "MY_APP_V_1.0": - workflow = serializer.deserialize_json(some_json) - else: - workflow = old_serializer.deserialize_workflow(some_json, workflow_spec=spec) - - new_json = serializer.serialize_json(workflow) - -Because the serializer is highly customizable, we've made it possible for you to manage your own versions of the -serialization. You can do this by passing a version number into the serializer, which will be embedded in the -json of all workflows. This allows you to modify the serialization and customize it over time, and still manage -the different forms as you make adjustments without leaving people behind. - -Versioned Serializer -^^^^^^^^^^^^^^^^^^^^ - -As we make changes to Spiff, we may change the serialization format. For example, in 1.1.8, we changed -how subprocesses were handled interally in BPMN workflows and updated how they are serialized. If you have -not overridden our version number with one of your own, the serializer will transform the 1.0 format to the -new 1.1 format. +If you have not provided a custom version number, SpiffWorkflow wil attempt to migrate your workflows from one version +to the next if they were serialized in an earlier format. If you've overridden the serializer version, you may need to incorporate our serialization changes with your own. You can find our conversions in -`version_migrations.py `_ +`SpiffWorkflow/bpmn/serilaizer/migrations `_ + +These are broken up into functions that handle each individual change, which will hopefully make it easier to incoporate them +into your upgrade process, and also provides some documentation on what has changed. + +Custom Serialization in More Depth +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Both of the serializer components mentioned in `Serialization`_ are based on the :code:`DictionaryConverter`. Let's import +it and create one and register a type: + +.. code:: python + + from datetime import datetime + + from SpiffWorkflow.bpmn.serializer.helpers.dictionary import DictionaryConverter + registry = DictionaryConverter() + registry.register( + datetime. + lambda dt: {'value': dt.isoformat() }, + lambda dct: datetime.fromisoformat(dct['value']) + ) + +The arguemnts to :code:`register` are: + +* `cls`: the class to be converted +* `to_dict`: a function that returns a dictionary containing JSON-serializable objects +* `from_dict`: a function that take the output of `to_dict` and restores the original object + +When the :code:`register` method is called, a `typename` is created and maps are set up between `cls` and `to_dict` +function, `cls` and `typename`, and `typename` and `from_dict`. + +When :code:`registry.convert` is called on an object, the `cls` is use to retrieve the `to_dict` function and the +`typename`. The `to_dict` funciton is called on the object and the `typename` is added to the resulting dictionary. + +When :code:`registry.restore` is called with a dictionary, it is checked for a `typename` key, and if one exists, it +is used to retrieve the `from_dict` function and the dictionary is passed to it. + +If an object is not recognized, it will be passed on as-is. Custom Script Engines --------------------- @@ -269,20 +273,30 @@ security reasons. .. warning:: - The default script engine does little to no sanitization and uses :code:`eval` - and :code:`exec`! If you have security concerns, you should definitely investigate - replacing the default with your own implementation. + By default, the scripting environment passes input directly to :code:`eval` and :code:`exec`! In most + cases, you'll want to replace the default scripting environment with one of your own. -We'll cover a simple extension of custom script engine here. There is also an example of -a similar engine based on `RestrictedPython `_ -included alongside this example. +Files referenced in this section: -The default script engine does not import any objects. +* `script_engine.py `_ +* `product_info.py `_ +* `subprocess.py `_ +* `spiff-bpmn-runner.py `_ -You could add functions or classes from the standard python modules or any code you've -implemented yourself. Your global environment can be passed in using the `default_globals` -argument when initializing the script engine. In our RestrictedPython example, we use their -`safe_globals` which prevents users from executing some potentially unsafe operations. +The following example replaces the default global enviroment with the one provided by +`RestrictedPython `_ + +.. code:: python + + from RestrictedPython import safe_globals + from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine + from SpiffWorkflow.bpmn.PythonScriptEngineEnvironment import TaskDataEnvironment + + restricted_env = TaskDataEnvironment(safe_globals) + restricted_script_engine = PythonScriptEngine(environment=restricted_env) + +Another reason you might want to customize the scripting environment is to provide access to custom +classes or functions. In our example models so far, we've been using DMN tables to obtain product information. DMN tables have a **lot** of uses so we wanted to feature them prominently, but in a simple way. @@ -293,20 +307,21 @@ our diagram (although it is much easier to modify the BPMN diagram than to chang itself!). Our shipping costs would not be static, but would depend on the size of the order and where it was being shipped -- maybe we'd query an API provided by our shipper. -SpiffWorkflow is obviously **not** going to know how to make a call to **your** database or -make API calls to **your** vendors. However, you can implement the calls yourself and make them -available as a method that can be used within a script task. +SpiffWorkflow is obviously **not** going to know how to query **your** database or make API calls to +**your** vendors. However, one way of making this functionality available inside your diagram is to +implement the calls in functions and add those functions to the scripting environment, where they +can be called by Script Tasks. We are not going to actually include a database or API and write code for connecting to and querying -it, but we can model our database with a simple dictionary lookup since we only have 7 products +it, but since we only have 7 products we can model our database with a simple dictionary lookup and just return the same static info for shipping for the purposes of the tutorial. +We'll define some resources in `product_info.py` + .. code:: python from collections import namedtuple - from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine - ProductInfo = namedtuple('ProductInfo', ['color', 'size', 'style', 'price']) INVENTORY = { @@ -325,92 +340,153 @@ and just return the same static info for shipping for the purposes of the tutori def lookup_shipping_cost(shipping_method): return 25.00 if shipping_method == 'Overnight' else 5.00 - additions = { - 'lookup_product_info': lookup_product_info, - 'lookup_shipping_cost': lookup_shipping_cost - } - - CustomScriptEngine = PythonScriptEngine(scripting_additions=additions) - -We pass the script engine we created to the workflow when we load it. +We'll add these functions to our scripting environment in `script_engine.py` .. code:: python - return BpmnWorkflow(parser.get_spec(process), script_engine=CustomScriptEngine) + env_globals = { + 'lookup_product_info': lookup_product_info, + 'lookup_shipping_cost': lookup_shipping_cost, + 'datetime': datetime, + } + custom_env = TaskDataEnvironment(env_globals) + custom_script_engine = PythonScriptEngine(environment=custom_env) -We can use the custom functions in script tasks like any normal function: +.. note:: -.. figure:: figures/custom_script_usage.png + We're also adding :code:`datetime`, because we added the timestamp to the payload of our message when we + set up the Message Event (see :doc:`events`) + +When we initialize the runner in `spiff-bpmn-runner.py`, we'll import and use `cusrom_script_engine` as our +script engine. + +We can use the custom functions in script tasks like any normal function. We've replaced the Business Rule +Task that determines product price with a script that simply checks the `price` field on our product. + +.. figure:: figures/script_engine/top_level.png :scale: 30% :align: center - Workflow with lanes + Top Level Workflow with Custom Script Engine -And we can simplify our 'Call Activity' flows: +And we can simplify the gateways in our 'Call Activity' flows as well now too: -.. figure:: figures/call_activity_script_flow.png +.. figure:: figures/script_engine/call_activity.png :scale: 30% :align: center - Workflow with lanes + Call Activity with Custom Script Engine -To run this workflow: +To run this workflow (you'll have to manually change which script engine you import): .. code-block:: console - ./run.py -p order_product -b bpmn/call_activity_script.bpmn bpmn/top_level_script.bpmn + ./spiff-bpmn-runner.py -p order_product -b bpmn/tutorial/top_level_script.bpmn bpmn/tutorial/call_activity_script.bpmn -It is also possible to completely replace `exec` and `eval` with something else, or to -execute or evaluate statements in a completely separate environment by subclassing the -:code:`PythonScriptEngine` and overriding `_execute` and `_evaluate`. We have examples of -executing code inside a docker container or in a celery task i this repo. +Another reason to customize the scripting enviroment is to allow it to run completely separately from +SpiffWorkflow. You might wish to do this for performance or security reasons. -MultiInstance Notes -------------------- +In our example repo, we've created a simple command line script in `runner/subprocess.py` that takes serialized global +and local environments and a script or expression to execute or evaluate. In `runner/script_engine.py`, we create +a scripting environment that runs the current :code:`execute` or :code:`evaluate` request in a subprocess with this +script. We've imported our custom methods into `subprocess.py` so they are automatically available when it is used. -**loopCardinality** - This variable can be a text representation of a -number - for example '2' or it can be the name of a variable in -task.data that resolves to a text representation of a number. -It can also be a collection such as a list or a dictionary. In the -case that it is a list, the loop cardinality is equal to the length of -the list and in the case of a dictionary, it is equal to the list of -the keys of the dictionary. +This example is needlessly complex for the work we're doing in this case, but the point of the example is to demonstrate +that this could be a Docker container with a complex environment, an HTTP API running somewhere else entirely. -If loopCardinality is left blank and the Collection is defined, or if -loopCardinality and Collection are the same collection, then the -MultiInstance will loop over the collection and update each element of -that collection with the new information. In this case, it is assumed -that the incoming collection is a dictionary, currently behavior for -working with a list in this manner is not defined and will raise an error. +.. note:: -**Collection** This is the name of the collection that is created from -the data generated when the task is run. Examples of this would be -form data that is generated from a UserTask or data that is generated -from a script that is run. Currently the collection is built up to be -a dictionary with a numeric key that corresponds to the place in the -loopCardinality. For example, if we set the loopCardinality to be a -list such as ['a','b','c] the resulting collection would be {1:'result -from a',2:'result from b',3:'result from c'} - and this would be true -even if it is a parallel MultiInstance where it was filled out in a -different order. + Note that our execute method returns :code:`True`. We could check the status of our process here and return + :code:`False` to force our task into an `ERROR` state if the task failed to execute. -**Element Variable** This is the variable name for the current -iteration of the MultiInstance. In the case of the loopCardinality -being just a number, this would be 1,2,3, . . . If the -loopCardinality variable is mapped to a collection it would be either -the list value from that position, or it would be the value from the -dictionary where the keys are in sorted order. It is the content of the -element variable that should be updated in the task.data. This content -will then be added to the collection each time the task is completed. + We could also return :code:`None` + if the task is not finished; this will cause the task to go into the `STARTED` state. You would have to manually + complete a task that has been `STARTED`. The purpose of the state is to tell SpiffWorkflow your application will + handle monitoring and updating this task and other branches that do not depend on this task may proceed. It is + intended to be used with potentially long-running tasks. -Example: - In a sequential MultiInstance, loop cardinality is ['a','b','c'] and elementVariable is 'myvar' - then in the case of a sequential multiinstance the first call would - have 'myvar':'a' in the first run of the task and 'myvar':'b' in the - second. + See :doc:`../concepts` for more information about Task States and Workflow execution. + +Service Tasks +------------- + +Service Tasks are also executed by the workflow's script engine, but through a different method, with the help of some +custom extensions in the :code:`spiff` package: + +- `operation_name`, the name assigned to the service being called +- `operation_params`, the parameters the operation requires + + +This is our script engine and scripting environment: + +.. code:: python + + service_task_env = TaskDataEnvironment({ + 'product_info_from_dict': product_info_from_dict, + 'datetime': datetime, + }) + + class ServiceTaskEngine(PythonScriptEngine): + + def __init__(self): + super().__init__(environment=service_task_env) + + def call_service(self, operation_name, operation_params, task_data): + if operation_name == 'lookup_product_info': + product_info = lookup_product_info(operation_params['product_name']['value']) + result = product_info_to_dict(product_info) + elif operation_name == 'lookup_shipping_cost': + result = lookup_shipping_cost(operation_params['shipping_method']['value']) + else: + raise Exception("Unknown Service!") + return json.dumps(result) + + service_task_engine = ServiceTaskEngine() + +Instead of adding our custom functions to the enviroment, we'll override :code:`call_service` and call them directly +according to the `operation_name` that was given. The :code:`spiff` Service Task also evaluates the parameters +against the task data for us, so we can pass those in directly. The Service Task will also store our result in +a user-specified variable. + +We need to send the result back as json, so we'll reuse the functions we wrote for the serializer. + +The Service Task will assign the dictionary as the operation result, so we'll add a `postScript` to the Service Task +that retrieves the product information that creates a :code:`ProductInfo` instance from the dictionary, so we need to +import that too. + +The XML for the Service Task looks like this: + +.. code:: xml + + + + + + + + + product_info = product_info_from_dict(product_info) + + Flow_104dmrv + Flow_06k811b + + +Getting this information into the XML is a little bit beyond the scope of this tutorial, as it involves more than +just SpiffWorkflow. I hand edited it for this case, but you can hardly ask your BPMN authors to do that! + +Our `modeler `_ has a means of providing a list of services and +their parameters that can be displayed to a BPMN author in the Service Task configurtion panel. There is an example of +hard-coding a list of services in +`app.js `_ +and as suggested, it would be reasonably straightforward to replace this with a API call. `SpiffArena `_ +has robust mechanisms for handling this that might serve as a model for you. + +How this all works is obviously heavily dependent on your application, so we won't go into further detail here, except +to give you a bare bones starting point for implementing something yourself that meets your own needs. + +To run this workflow (you'll have to manually change which script engine you import): + +.. code-block:: console + + ./spiff-bpmn-runner.py -p order_product -b bpmn/tutorial/top_level_service_task.bpmn bpmn/tutorial/call_activity_service_task.bpmn -Example: - In a Parallel MultiInstance, Loop cardinality is a variable that contains - {'a':'A','b':'B','c':'C'} and elementVariable is 'myvar' - when the multiinstance is ready, there - will be 3 tasks. If we choose the second task, the task.data will - contain 'myvar':'B'. diff --git a/SpiffWorkflow/doc/bpmn/camunda/events.rst b/SpiffWorkflow/doc/bpmn/camunda/events.rst new file mode 100644 index 000000000..f6367ffb9 --- /dev/null +++ b/SpiffWorkflow/doc/bpmn/camunda/events.rst @@ -0,0 +1,45 @@ +Events +====== + +Message Events +-------------- + +Configuring Message Events +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. figure:: figures/throw_message_event.png + :scale: 60% + :align: center + + Throw Message Event configuration + + +.. figure:: figures/message_start_event.png + :scale: 60% + :align: center + + Message Catch Event configuration + +The Throw Message Event Implementation should be 'Expression' and the Expression should +be a Python statement that can be evaluated. In this example, we'll just send the contents +of the :code:`reason_delayed` variable, which contains the response from the 'Investigate Delay' +Task. + +We can provide a name for the result variable, but I have not done that here, as it does not +make sense to me for the generator of the event to tell the handler what to call the value. +If you *do* specify a result variable, the message payload (the expression evaluated in the +context of the Throwing task) will be added to the handling task's data in a variable of that +name; if you leave it blank, SpiffWorkflow will create a variable of the form _Response. + +Running the Model +^^^^^^^^^^^^^^^^^ + +If you have set up our example repository, this model can be run with the +following command: + +.. code-block:: console + + ./camunda-bpmn-runner.py -c order_collaboration \ + -d bpmn/tutorial/product_prices.dmn bpmn/tutorial/shipping_costs.dmn \ + -b bpmn/camunda/events.bpmn bpmn/camunda/call_activity.bpmn \ No newline at end of file diff --git a/SpiffWorkflow/doc/bpmn/figures/call_activity_multi.png b/SpiffWorkflow/doc/bpmn/camunda/figures/call_activity_multi.png similarity index 100% rename from SpiffWorkflow/doc/bpmn/figures/call_activity_multi.png rename to SpiffWorkflow/doc/bpmn/camunda/figures/call_activity_multi.png diff --git a/SpiffWorkflow/doc/bpmn/figures/documentation.png b/SpiffWorkflow/doc/bpmn/camunda/figures/documentation.png similarity index 100% rename from SpiffWorkflow/doc/bpmn/figures/documentation.png rename to SpiffWorkflow/doc/bpmn/camunda/figures/documentation.png diff --git a/SpiffWorkflow/doc/bpmn/figures/documentation_multi.png b/SpiffWorkflow/doc/bpmn/camunda/figures/documentation_multi.png similarity index 100% rename from SpiffWorkflow/doc/bpmn/figures/documentation_multi.png rename to SpiffWorkflow/doc/bpmn/camunda/figures/documentation_multi.png diff --git a/SpiffWorkflow/doc/bpmn/figures/message_start_event.png b/SpiffWorkflow/doc/bpmn/camunda/figures/message_start_event.png similarity index 100% rename from SpiffWorkflow/doc/bpmn/figures/message_start_event.png rename to SpiffWorkflow/doc/bpmn/camunda/figures/message_start_event.png diff --git a/SpiffWorkflow/doc/bpmn/figures/multiinstance_task_configuration.png b/SpiffWorkflow/doc/bpmn/camunda/figures/multiinstance_task_configuration.png similarity index 100% rename from SpiffWorkflow/doc/bpmn/figures/multiinstance_task_configuration.png rename to SpiffWorkflow/doc/bpmn/camunda/figures/multiinstance_task_configuration.png diff --git a/SpiffWorkflow/doc/bpmn/figures/throw_message_event.png b/SpiffWorkflow/doc/bpmn/camunda/figures/throw_message_event.png similarity index 100% rename from SpiffWorkflow/doc/bpmn/figures/throw_message_event.png rename to SpiffWorkflow/doc/bpmn/camunda/figures/throw_message_event.png diff --git a/SpiffWorkflow/doc/bpmn/figures/user_task.png b/SpiffWorkflow/doc/bpmn/camunda/figures/user_task.png similarity index 100% rename from SpiffWorkflow/doc/bpmn/figures/user_task.png rename to SpiffWorkflow/doc/bpmn/camunda/figures/user_task.png diff --git a/SpiffWorkflow/doc/bpmn/camunda/multiinstance.rst b/SpiffWorkflow/doc/bpmn/camunda/multiinstance.rst new file mode 100644 index 000000000..bdae25cb7 --- /dev/null +++ b/SpiffWorkflow/doc/bpmn/camunda/multiinstance.rst @@ -0,0 +1,30 @@ +MultiInstance Tasks +=================== + +Earlier versions of SpiffWorkflow relied on the properties available in the Camunda MultiInstance Panel. + +.. figure:: figures/multiinstance_task_configuration.png + :scale: 60% + :align: center + + MultiInstance Task configuration + +SpiffWorkflow has a MultiInstance Task spec in the :code:`camunda` package that interprets these fields +in the following way: + +* Loop Cardinality: + + - If this is an integer, or a variable that evaluates to an integer, this number would be + used to determine the number of instances + - If this is a collection, the size of the collection would be used to determine the number of + instances + +* Collection: the output collection (input collections have to be specified in the "Cardinality" field). + +* Element variable: the name of the varible to copy the item into for each instance. + +.. warning:: + + The spec in this package is based on an old version of Camunda, which might or might not have been the + way Camunda uses these fields, and may or may not be similar to newer or current versions. + *Use at your own risk!* diff --git a/SpiffWorkflow/doc/bpmn/camunda/support.rst b/SpiffWorkflow/doc/bpmn/camunda/support.rst new file mode 100644 index 000000000..6768920cd --- /dev/null +++ b/SpiffWorkflow/doc/bpmn/camunda/support.rst @@ -0,0 +1,23 @@ +Camunda Editor Support +====================== + +.. warning:: There is a better way ... + SpiffWorkflow does not aim to support all of Camunda's proprietary extensions. + Many of of the items in the Camunda Properties Panel do not work. And + major features of SpiffWorkflow (Messages, Data Objects, Service Tasks, Pre-Scripts, etc...) + can not be configured in the Camunda editor. Use `SpiffArena `_ + to build and test your BPMN models instead! + +Earlier users of SpiffWorkflow relied heavily on Camunda's modeler and several of our task spec +implementations were based on Camunda's extensions. Support for these extensions has been moved +to the :code:`camunda` package. We are not actively maintaining this package (though we will +accept contributions from Camunda users!). Please be aware that many of the Camunda extensions +that will appear in the Camunda editor do not work with SpiffWorkflow. + + +.. toctree:: + :maxdepth: 3 + + tasks + events + multiinstance diff --git a/SpiffWorkflow/doc/bpmn/camunda/tasks.rst b/SpiffWorkflow/doc/bpmn/camunda/tasks.rst new file mode 100644 index 000000000..0962a8fad --- /dev/null +++ b/SpiffWorkflow/doc/bpmn/camunda/tasks.rst @@ -0,0 +1,104 @@ +Tasks +===== + +User Tasks +---------- + +Creating a User Task +^^^^^^^^^^^^^^^^^^^^ + +When you click on a user task in the BPMN modeler, the Properties Panel includes a form tab. Use this +tab to build your questions. + +The following example shows how a form might be set up in Camumda. + +.. figure:: figures/user_task.png + :scale: 30% + :align: center + + User Task configuration + + +Manual Tasks +------------ + +Creating a Manual Task +^^^^^^^^^^^^^^^^^^^^^^ + +We can use the BPMN element Documentation field to display more information about the context of the item. + +Spiff is set up in a way that you could use any templating library you want, but we have used +`Jinja `_. + +In this example, we'll present an order summary to our customer. + +.. figure:: figures/documentation.png + :scale: 30% + :align: center + + Element Documentation + +Running The Model +----------------- + +If you have set up our example repository, this model can be run with the +following command: + +.. code-block:: console + + ./camunda-bpmn-runner.py -p order_product -d bpmn/tutorial/product_prices.dmn -b bpmn/camunda/task_types.bpmn + +Example Application Code +------------------------ + +Handling the User Task +^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + dct = {} + for field in task.task_spec.form.fields: + if isinstance(field, EnumFormField): + option_map = dict([ (opt.name, opt.id) for opt in field.options ]) + options = "(" + ', '.join(option_map) + ")" + prompt = f"{field.label} {options} " + option = input(prompt) + while option not in option_map: + print(f'Invalid selection!') + option = input(prompt) + response = option_map[option] + else: + response = input(f"{field.label} ") + if field.type == "long": + response = int(response) + update_data(dct, field.id, response) + DeepMerge.merge(task.data, dct) + +The list of form fields for a task is stored in :code:`task.task_spec.form_fields`. + +For Enumerated fields, we want to get the possible options and present them to the +user. The variable names of the fields were stored in :code:`field.id`, but since +we set labels for each of the fields, we'd like to display those instead, and map +the user's selection back to the variable name. + +For other fields, we'll just store whatever the user enters, although in the case +where the data type was specified to be a :code:`long`, we'll convert it to a +number. + +Finally, we need to explicitly store the user-provided response in a variable +with the expected name with :code:`update_data(dct, field.id, response)` and merge +the newly collected data into our task data with :code:`DeepMerge.merge(task.data, dct)`. + +Our :code:`update_data` function handles "dot notation" in field names, which creates +nested dictionaries based on the path components. + +.. code:: python + + def update_data(dct, name, value): + path = name.split('.') + current = dct + for component in path[:-1]: + if component not in current: + current[component] = {} + current = current[component] + current[path[-1]] = value diff --git a/SpiffWorkflow/doc/bpmn/custom_task_spec.rst b/SpiffWorkflow/doc/bpmn/custom_task_spec.rst new file mode 100644 index 000000000..b575ada71 --- /dev/null +++ b/SpiffWorkflow/doc/bpmn/custom_task_spec.rst @@ -0,0 +1,133 @@ +Implementing a Custom Task Spec +------------------------------- + +Suppose we wanted to manage Timer Start Events outside of SpiffWorkflow. If we have a process loaded up and running that +starts with a timer, the timer waits until the event occurs; this might be days or weeks later. + +Of course, we can always check that it's waiting and serialize the workflow until that time. However, we might decide that +we don't want SpiffWorkflow to manage this at all. We could do this with a custom task spec. + +First we'll create a new class + +.. code:: python + + from SpiffWorkflow.bpmn.specs.event_definitions import TimerEventDefinition, NoneEventDefinition + from SpiffWorkflow.bpmn.specs.mixins.events.start_event import StartEvent + from SpiffWorkflow.spiff.specs.spiff_task import SpiffBpmnTask + + class CustomStartEvent(StartEvent, SpiffBpmnTask): + + def __init__(self, wf_spec, bpmn_id, event_definition, **kwargs): + + if isinstance(event_definition, TimerEventDefinition): + super().__init__(wf_spec, bpmn_id, NoneEventDefinition(), **kwargs) + self.timer_event = event_definition + else: + super().__init__(wf_spec, bpmn_id, event_definition, **kwargs) + self.timer_event = None + +When we create our custom event, we'll check to see if we're creating a Start Event with a TimerEventDefinition, and if so, +we'll replace it with a NoneEventDefinition. + +.. note:: + + Our class inherits from two classes. We import a mixin class that defines generic BPMN Start Event behavior from + :code:`StartEvent` in the :code:`bpmn` package and the :code:`SpiffBpmnTask` from the :code:`spiff` package, which + extends the default :code:`BpmnSpecMixin`. + + We've split the basic behavior for specific BPMN tasks from the :code:`BpmnSpecMixin` to make it easier to extend + them without running into MRO issues. + + In general, if you implement a custom task spec, you'll need to inherit from bases of both categories. + +Whenever we create a custom task spec, we'll need to create a converter for it so that it can be serialized. + +.. code:: python + + from SpiffWorkflow.bpmn.serializer.workflow import BpmnWorkflowSerializer + from SpiffWorkflow.bpmn.serializer.task_spec import StartEventConverter + from SpiffWorkflow.spiff.serializer.task_spec import SpiffBpmnTaskConverter + from SpiffWorkflow.spiff.serializer.config import SPIFF_SPEC_CONFIG + + class CustomStartEventConverter(SpiffBpmnTaskConverter): + + def __init__(self, registry): + super().__init__(CustomStartEvent, registry) + + def to_dict(self, spec): + dct = super().to_dict(spec) + if spec.timer_event is not None: + dct['event_definition'] = self.registry.convert(spec.timer_event) + else: + dct['event_definition'] = self.registry.convert(spec.event_definition) + return dct + + + SPIFF_SPEC_CONFIG['task_specs'].remove(StartEventConverter) + SPIFF_SPEC_CONFIG['task_specs'].append(CustomStartEventConverter) + + wf_spec_converter = BpmnWorkflowSerializer.configure_workflow_spec_converter(SPIFF_SPEC_CONFIG) + serializer = BpmnWorkflowSerializer(wf_spec_converter) + +Our converter will inherit from the :code:`SpiffBpmnTaskConverter`, since that's our base generic BPMN mixin class. + +The :code:`SpiffBpmnTaskConverter` ultimately inherits from +:code:`SpiffWorkflow.bpmn.serializer.helpers.task_spec.BpmnTaskSpecConverter`. which provides some helper methods for +extracting standard attributes from tasks; the :code:`SpiffBpmnTaskConverter` does the same for extensions from the +:code:`spiff` package. + +We don't have to do much -- all we do is replace the event definition with the original. The timer event will be +moved when the task is restored. + +.. note:: + + It might be better have the class's init method take both the event definition to use *and* the timer event + definition. Unfortunately, our parser is not terribly intuitive or easily extendable, so I've done it this + way to make this a little easier to follow. + +When we create our serializer, we need to tell it about this task. We'll remove the converter for the standard Start +Event and add the one we created to the confiuration and create the :code:`workflow_spec_converter` from the updated +config. + +.. note:: + + We have not instantiated our converter class. When we call :code:`configure_workflow_spec_converter` with a + configuration (which is essentially a list of classes, split up into sections for organizational purposes), + *it* instantiates the classes for us, using the same `registry` for every class. At the end of the configuration + if returns this registry, which now knows about all of the classes that will be used for SpiffWorkflow + specifications. It is possible to pass a separately created :code:`DictionaryConverter` preconfigured with + other converters; in that case, it will be used as the base `registry`, to which specification conversions will + be added. + +Because we've built up the `registry` in such a way, we can make use of the :code:`registry.convert` and +:code:`registry.restore` methods rather than figuring out how to serialize them. We can use these methods on any +objects that SpiffWorkflow knows about. + +See :doc:`advanced` for more information about the serializer. + +Finally, we have to update our parser: + +.. code:: python + + from SpiffWorkflow.spiff.parser.event_parsers import StartEventParser + from SpiffWorkflow.spiff.parser.process import SpiffBpmnParser + from SpiffWorkflow.bpmn.parser.util import full_tag + + parser = SpiffBpmnParser() + parser.OVERRIDE_PARSER_CLASSES[full_tag('startEvent')] = (StartEventParser, CustomStartEvent) + +The parser contains class attributes that define how to parse a particular element and the class that should be used to +create the task spec, so rather than pass these in as arguments, we create a parser and then update the values it +will use. This is a bit unintuitive, but that's how it works. + +Fortunately, we were able to reuse an existing Task Spec parser, which simplifies the process quite a bit. + +Having created a parser and serializer, we could replace the ones we pass in the the :code:`SimpleBpmnRunner` with these. + +I am going to leave creating a script that makes use of them to readers of this document, as it should be clear enough +how to do. + +There is a very simple diagram `bpmn/tutorial/timer_start.bpmn` with the process ID `timer_start` with a Start Event +with a Duration Timer of one day that can be used to illustrate how the custom task works. If you run this workflow +with `spiff-bpmn-runner.py`, you'll see a `WAITING` Start Event; if you use the parser and serializer we just created, +you'll be propmted to complete the User Task immediately. \ No newline at end of file diff --git a/SpiffWorkflow/doc/bpmn/data.rst b/SpiffWorkflow/doc/bpmn/data.rst new file mode 100644 index 000000000..3fcf0beb0 --- /dev/null +++ b/SpiffWorkflow/doc/bpmn/data.rst @@ -0,0 +1,98 @@ +Data +==== + +BPMN Model +---------- + +We'll be using the following files from `spiff-example-cli `_. + +- `bpmn-spiff/events `_ workflow +- `bpmn-spiff/call_activity `_ workflow +- `bpmn-spiff/data_output `_ workflow +- `product_prices `_ DMN table +- `shipping_costs `_ DMN table + + +Data Objects +^^^^^^^^^^^^ + + Data Objects exist at the process level and are not visible in the diagram, but when you create a Data Object + Reference, you can choose what Data Object it points to. + +.. figure:: figures/data/data_object_configuration.png + :scale: 50% + :align: center + + Configuring a Data Object Reference + +When a Data Output association (a line) is drawn from a task to a Data Object Reference, the value is copied +from the task data to the workflow data and removed from the task. If a Data Input Association is created from +a Data Object Reference, the value is temporarily copied into the task data while the task is being executed, +and immediate removed afterwards. + +This allows sensitive data to be removed from individual tasks (in our example, the customer's credit card +number). It can also be used to prevent large objects from being repeatedly copied from task to task. + +Multiple Data Object References can point to the same underlying data. In our example, we use two references +to the same Data Object to pass the credit card info to both tasks that require it. On the right panel, we can +see that only one data object exists in the process. + +.. figure:: figures/data/data_objects.png + :scale: 30% + :align: center + + Data objects in a process + +If you step through this workflow, you'll see that the card number is not contained in the task data after +the 'Enter Payment Info' has been completed but is available to the 'Charge Customer' task later on. + +Running The Model +***************** + +If you have set up our example repository, this model can be run with the following command: + +.. code-block:: console + + ./spiff-bpmn-runner.py -c order_collaboration \ + -d bpmn/tutorial/product_prices.dmn bpmn/tutorial/shipping_costs.dmn \ + -b bpmn/tutorial/events.bpmn bpmn/tutorial/call_activity.bpmn + + +Data Inputs and Outputs +^^^^^^^^^^^^^^^^^^^^^^^ + +In complex workflows, it is useful to be able to specify required Data Inputs and Outputs, especially for Call Activities +given that they are external and might be shared across many different processes. + +When you add a Data Input to a Call Activity, SpiffWorkflow will check that a variable with that name is available to +be copied into the activity and copy *only* the variables you've specified as inputs. When you add a Data Output, +SpiffWorkflow will copy *only* the variables you've specified from the Call Activity at the end of the process. If any +of the variables are missing, SpiffWorkflow will raise an error. + +Our product customization Call Activity does not require any input, but the output of the process is the product +name and quantity. We can add corresponding Data Outputs for those. +.. figure:: figures/data/data_output.png + :scale: 30% + :align: center + + Data Outputs in a Call Activity + +If you use this version of the Call Activity and choose a product that has customizations, when you inspect the data +after the Call Activity completes, you'll see that the cutomizations have been removed. We won't continue to use this +version of the Call Activity, because we want to preserve all the data. + +.. note:: + + The BPMN spec allows *any* task to have Data Inputs and Outputs. Our modeler does not provide a way to add them to + arbitrary tasks, but SpiffWorkflow will recognize them on any task if they are present in the BPMN XML. + +Running The Model +***************** + +If you have set up our example repository, this model can be run with the following command: + +.. code-block:: console + + ./spiff-bpmn-runner.py -p order_product \ + -d bpmn/tutorial/product_prices.dmn bpmn/tutorial/shipping_costs.dmn \ + -b bpmn/tutorial/top_level.bpmn bpmn/tutorial/data_output.bpmn diff --git a/SpiffWorkflow/doc/errors.rst b/SpiffWorkflow/doc/bpmn/errors.rst similarity index 74% rename from SpiffWorkflow/doc/errors.rst rename to SpiffWorkflow/doc/bpmn/errors.rst index 31c606de2..41afeafb8 100644 --- a/SpiffWorkflow/doc/errors.rst +++ b/SpiffWorkflow/doc/bpmn/errors.rst @@ -1,13 +1,14 @@ SpiffWorkflow Exceptions -==================================== +======================== + Details about the exceptions and exception hierarchy within SpiffWorkflow SpiffWorkflowException ----------- +---------------------- Base exception for all exceptions raised by SpiffWorkflow ValidationException ----------- +------------------- **Extends** SpiffWorkflowException @@ -25,7 +26,7 @@ Thrown during the parsing of a workflow. WorkflowException --------- +----------------- When an error occurs with a Task Specification (maybe should have been called a SpecException) @@ -34,13 +35,12 @@ SpiffWorkflowException **Attributes/Methods** -- **sender**: The TaskSpec - the specific Task, Gateway, etc... that caused the error to happen. +- **task_spec**: The TaskSpec - the specific Task, Gateway, etc... that caused the error to happen. - **error**: a human readable error message describing the problem. -- **get_task_trace**: Provided a specific Task, will work it's way through the workflow / sub-processes -and call activities to show where an error occurred. Useful if the error happened within a deeply nested structure (where call activities include call activities ....) + WorkflowDataException ------------------- +--------------------- When an exception occurs moving data between tasks and Data Objects (including data inputs and data outputs.) @@ -56,10 +56,16 @@ WorkflowException - **data_output**: The spec of the output variable WorkflowTaskException --------- +--------------------- **Extends** WorkflowException +It will accept the line_number and error_line as arguments - if the +underlying error provided is a SyntaxError it will try to derive this +information from the error. +If this is a name error, it will attempt to calculate a did-you-mean +error_msg. + **Attributes/Methods** (in addition to the values in a WorkflowException) @@ -70,21 +76,8 @@ WorkflowException - **line_number** The line number that contains the error - **offset** The point in the line that caused the error - **error_line** The content of the line that caused the error. - -It will accept the line_number and error_line as arguments - if the -underlying error provided is a SyntaxError it will try to derive this -information from the error. -If this is a name error, it will attempt to calculate a did-you-mean -error_msg. - -Unused / Deprecated errors --------------------- - -** StorageException ** -Deprecated -- Used only by the PrettyXmlSerializer - which is not under active -support. - -** DeadMethodCalled ** -Something related to WeakMethod -- which doesn't look to be utilized anymore. - + - **get_task_trace**: Provided a specific Task, will work it's way through the workflow/sub-processes and + call activities to show where an error occurred. Useful if the error happened within a deeply nested + structure (where call activities include call activities ....) + - **did_you_mean_name_error**: Compares a missing data value with the contents of the data diff --git a/SpiffWorkflow/doc/bpmn/events.rst b/SpiffWorkflow/doc/bpmn/events.rst index a2b32f4c7..0e8b5568b 100644 --- a/SpiffWorkflow/doc/bpmn/events.rst +++ b/SpiffWorkflow/doc/bpmn/events.rst @@ -6,14 +6,14 @@ BPMN Model We'll be using the following files from `spiff-example-cli `_. -- `transaction `_ workflow -- `signal_event `_ workflow -- `events `_ workflow -- `call activity `_ workflow -- `product_prices `_ DMN table -- `shipping_costs `_ DMN table +- `transaction `_ workflow +- `signal_event `_ workflow +- `events `_ workflow +- `call activity `_ workflow +- `product_prices `_ DMN table +- `shipping_costs `_ DMN table -A general overview of events in BPMN can be found in the :doc:`/intro` +A general overview of events in BPMN can be found in the :doc:`overview` section of the documentation. SpiffWorkflow supports the following Event Definitions: @@ -28,11 +28,16 @@ SpiffWorkflow supports the following Event Definitions: We'll include examples of all of these types in this section. +.. note:: + + SpiffWorflow can also support Multiple Event definitions, but our modeler does not allow you to create them, + so we will not delve into them further here. + Transactions ^^^^^^^^^^^^ We also need to introduce the concept of a Transaction because certain events -can only be used in that context. A Transaction is essentially a subprocess, but +can only be used in that context. A Transaction is essentially a Subprocess, but it must fully complete before it affects its outer workflow. We'll make our customer's ordering process through the point they review their order @@ -46,20 +51,20 @@ only be used in Transactions. Cancel Events ^^^^^^^^^^^^^ -.. figure:: figures/transaction.png +.. figure:: figures/events/transaction.png :scale: 30% :align: center - Workflow with a transaction and Cancel Event + Workflow with a Transaction and Cancel Event We changed our 'Review Order' Task to be a User Task and have added a form, so that we can give the customer the option of cancelling the order. If the customer answers 'Y', then the workflow ends normally and we proceed to collecting payment information. -However, if the user elects to cancel their order, we use a 'Cancel End Event' -instead, which generates a Cancel Event. We can then attach a 'Cancel Boundary -Event' to the Transaction, and execute that path if the event occurs. Instead of +However, if the user elects to cancel their order, we use a Cancel End Event +instead, which generates a Cancel Event. We can then attach a Cancel Boundary +Event to the Transaction, and execute that path if the event occurs. Instead of asking the customer for their payment info, we'll direct them to a form and ask them why they cancelled their order. @@ -70,15 +75,15 @@ To run this workflow .. code-block:: console - ./run.py -p order_product \ - -d bpmn/product_prices.dmn bpmn/shipping_costs.dmn \ - -b bpmn/transaction.bpmn bpmn/call_activity.bpmn + ./spiff-bpmn-runner.py -p order_product \ + -d bpmn/tutorial/product_prices.dmn bpmn/tutorial/shipping_costs.dmn \ + -b bpmn/tutorial/transaction.bpmn bpmn/tutorial/call_activity.bpmn Signal Events ^^^^^^^^^^^^^ -.. figure:: figures/signal_event.png +.. figure:: figures/events/signal_event.png :scale: 30% :align: center @@ -91,43 +96,41 @@ Once the charge is placed, the task that provides the option to cancel will itself be cancelled when the charge event is received. We'll also need to detect the case that the customer cancels their order and -cancel the charge task if it occurs; we'll use a separate signal for that. +cancel the charge task if it occurs; we'll use a separate Signal for that. -Multiple tasks can catch the same signal event. Suppose we add a Manager role -to our workflow, and allow the Employee to refer unsuccessful charges to the +Multiple tasks can catch the same Signal Event. Suppose we add a Manager role +to our Process, and allow the Employee to refer unsuccessful charges to the Manager for resolution. The Manager's task will also need to catch the 'Order -Cancelled' signal event. +Cancelled' Signal Event. Signals are referred to by name. -.. figure:: figures/throw_signal_event.png - :scale: 30% +.. figure:: figures/events/throw_signal_event.png + :scale: 60% :align: center Signal Event configuration -.. Terminate Events: - Terminate Events ^^^^^^^^^^^^^^^^ We also added a Terminate Event to the Manager Workflow. A regular End Event simply marks the end of a path. A Terminate Event will indicate that the -entire workflow is complete and any remaining tasks should be cancelled. Our +entire Process is complete and any remaining tasks should be cancelled. Our customer cannot cancel an order that has already been cancelled, and we won't ask -them for feedback about it (we know it wasn't completed), so we do not want to -execute either of those tasks. - -We'll now modify our workflow to add an example of each of the other types of -events that SpiffWorkflow Supports. +them for feedback about it (we know that is was because we were unable to charge +them for it), so we do not want to execute either of those tasks. To run this workflow .. code-block:: console - ./run.py -p order_product \ - -d bpmn/product_prices.dmn bpmn/shipping_costs.dmn \ - -b bpmn/signal_event.bpmn bpmn/call_activity.bpmn + ./spiff-bpmn-runner.py -p order_product \ + -d bpmn/tutorial/product_prices.dmn bpmn/tutorial/shipping_costs.dmn \ + -b bpmn/tutorial/signal_event.bpmn bpmn/tutorial/call_activity.bpmn + +We'll now modify our workflow to add an example of each of the other types of +events that SpiffWorkflow supports. Error Events ^^^^^^^^^^^^ @@ -135,7 +138,7 @@ Error Events Let's turn to our order fulfillment subprocess. Either of these steps could potentially fail, and we may want to handle each case differently. -.. figure:: figures/events.png +.. figure:: figures/events/events.png :scale: 30% :align: center @@ -170,16 +173,16 @@ Escalation Boundary Event. Both Error and Escalation Events can be optionally associated with a code. Here is Throw Event for our `product_not_shipped` Escalation. -.. figure:: figures/throw_escalation_event.png - :scale: 30% +.. figure:: figures/events/throw_escalation_event.png + :scale: 60% :align: center Throw Escalation Event configuration Error Event configuration is similar. -If no code is provided in a Catch event, any event of the corresponding type will catch -the event. +If no code is provided in a Catch event, it can be caught by any Escalation with the same +name. Timer Events ^^^^^^^^^^^^ @@ -191,17 +194,21 @@ amount of time before continuing. We can use this as a regular Intermediate Eve this case, we simply want to notify the customer of the delay while continuing to process their order, so we use a Non-Interrupting Event. -.. figure:: figures/timer_event.png - :scale: 30% +.. figure:: figures/events/timer_event.png + :scale: 60% :align: center Duration Timer Event configuration -We express the duration as a Python :code:`timedelta`. We show the configuration for the Boundary -Event. +We express the duration as an ISO8601 duration. -It is also possible to use a static datetime to trigger an event. It will need to be parseable -as a date by Python. +.. note:: + + We enclosed the string in quotes, because it is possible to use a variable to determine + how long the timer should wait. + +It is also possible to use a static date and time to trigger an event. It will also need to be +specified in ISO8601 format. Timer events can only be caught, that is waited on. The timer begins implicitly when we reach the event. @@ -210,44 +217,63 @@ Message Events ^^^^^^^^^^^^^^ In BPMN, Messages are used to communicate across processes. Technically, Messages are not -intended to be used inside a single process, but Spiff does support this use. +intended to be used inside a single Process, but Spiff does support this use. -Messages are similar to signals, in that they are referenced by name, but they have the -additional property that they may contain a payload. +Messages are similar to Signals, in that they are referenced by name, but they have the +additional property that they may contain a payload. The payload is a bit of python code that will be +evaluated against the task data and sent along with the Message. In the corresponding Message Catch +Event or Receive Task, we define a variable name where we'll store the result. -We've added a QA process to our model, which will be initiated whenever an order takes to long -to fulfill. We'll send the reason for the delay in the message. +We've added a QA process to our model, which will be initiated whenever an order takes too long +to fulfill. We'll send the reason for the delay in the Message. -.. note:: +Spiff Messages can also optionally use Correlation Keys. The Correlation Key is an expression or set of +expressions that are evaluated against a Message payload to create an additional identifier for associating +messages with Processes. - This example depends on some Camunda-specific features in our implementation; there is - an alternate messaging implementation in the Spiff extensions package, described in - :doc:`spiff-extensions`. +In our example, it is possible that multiple QA processes could be started (the timer event will fire every +two minutes until the order fulfillment process is complete, or more realistically, they could be +investigating many entirely different orders, even if our simple runner does not handle that case). +In this case, the Message name is insufficient, as there will be multiple Processes that can accept +Messages based on the name. -.. figure:: figures/throw_message_event.png - :scale: 30% +.. figure:: figures/events/correlation.png + :scale: 50% :align: center - Throw Message Event configuration + Defining a correlation key -The Throw Message Event Implementation should be 'Expression' and the Expression should -be a Python statement that can be evaluated. In this example, we'll just send the contents -of the :code:`reason_delayed` variable, which contains the response from the 'Investigate Delay' -Task. +We use the timestamp of the Message creation as a unique key that can be used to distinguish between multiple +QA Processes. + +.. figure:: figures/events/throw_message_event.png + :scale: 50% + :align: center + + Configuring a message throw event + +When we receive the event, we assign the payload to :code:`order_info`. + +.. figure:: figures/events/catch_message_event.png + :scale: 50% + :align: center + + Configuring a message catch event + +The correlation is visible on both the Throw and Catch Events, but it is associated with the message rather +than the tasks themselves; if you update the expression on either event, the changes will appear in both places. -We can provide a name for the result variable, but I have not done that here, as it does not -make sense to me for the generator of the event to tell the handler what to call the value. -If you *do* specify a result variable, the message payload (the expression evaluated in the -context of the Throwing task) will be added to the handling task's data in a variable of that -name; if you leave it blank, SpiffWorkflow will create a variable of the form _Response. Running The Model ^^^^^^^^^^^^^^^^^ .. code-block:: console - ./run.py -p order_product \ - -d bpmn/product_prices.dmn bpmn/shipping_costs.dmn \ - -b bpmn/events.bpmn bpmn/call_activity.bpmn + ./spiff-bpmn-runner.py -c order_collaboration \ + -d bpmn/tutorial/product_prices.dmn bpmn/tutorial/shipping_costs.dmn \ + -b bpmn/tutorial/events.bpmn bpmn/tutorial/call_activity.bpmn +.. note:: + + We're specifying a collaboration rather than a process so that SpiffWorkflow knows that there is more than + one top-level process. diff --git a/SpiffWorkflow/doc/bpmn/figures/business_rule_task.png b/SpiffWorkflow/doc/bpmn/figures/business_rule_task.png deleted file mode 100644 index f76b8f62b..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/business_rule_task.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/call_activity.png b/SpiffWorkflow/doc/bpmn/figures/call_activity.png deleted file mode 100644 index 2bb55b957..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/call_activity.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/call_activity_script_flow.png b/SpiffWorkflow/doc/bpmn/figures/call_activity_script_flow.png deleted file mode 100644 index 68f555890..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/call_activity_script_flow.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/catch_esclalation_event.png b/SpiffWorkflow/doc/bpmn/figures/catch_esclalation_event.png deleted file mode 100644 index 646dc5d32..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/catch_esclalation_event.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/custom_script_usage.png b/SpiffWorkflow/doc/bpmn/figures/custom_script_usage.png deleted file mode 100644 index 4d7a6fa77..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/custom_script_usage.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/data_object_configuration.png b/SpiffWorkflow/doc/bpmn/figures/data/data_object_configuration.png similarity index 100% rename from SpiffWorkflow/doc/bpmn/figures/data_object_configuration.png rename to SpiffWorkflow/doc/bpmn/figures/data/data_object_configuration.png diff --git a/SpiffWorkflow/doc/bpmn/figures/data_objects.png b/SpiffWorkflow/doc/bpmn/figures/data/data_objects.png similarity index 100% rename from SpiffWorkflow/doc/bpmn/figures/data_objects.png rename to SpiffWorkflow/doc/bpmn/figures/data/data_objects.png diff --git a/SpiffWorkflow/doc/bpmn/figures/data/data_output.png b/SpiffWorkflow/doc/bpmn/figures/data/data_output.png new file mode 100644 index 000000000..a28836312 Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/data/data_output.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/dmn_table.png b/SpiffWorkflow/doc/bpmn/figures/dmn_table.png deleted file mode 100644 index 75e21c5e8..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/dmn_table.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/dmn_table_updated.png b/SpiffWorkflow/doc/bpmn/figures/dmn_table_updated.png deleted file mode 100644 index ae1049f4e..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/dmn_table_updated.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/events.png b/SpiffWorkflow/doc/bpmn/figures/events.png deleted file mode 100644 index 61041eb31..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/events.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/spiff_message_catch.png b/SpiffWorkflow/doc/bpmn/figures/events/catch_message_event.png similarity index 100% rename from SpiffWorkflow/doc/bpmn/figures/spiff_message_catch.png rename to SpiffWorkflow/doc/bpmn/figures/events/catch_message_event.png diff --git a/SpiffWorkflow/doc/bpmn/figures/correlation.png b/SpiffWorkflow/doc/bpmn/figures/events/correlation.png similarity index 100% rename from SpiffWorkflow/doc/bpmn/figures/correlation.png rename to SpiffWorkflow/doc/bpmn/figures/events/correlation.png diff --git a/SpiffWorkflow/doc/bpmn/figures/events/events.png b/SpiffWorkflow/doc/bpmn/figures/events/events.png new file mode 100644 index 000000000..e6b535652 Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/events/events.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/events/signal_event.png b/SpiffWorkflow/doc/bpmn/figures/events/signal_event.png new file mode 100644 index 000000000..05eedd15b Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/events/signal_event.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/events/throw_escalation_event.png b/SpiffWorkflow/doc/bpmn/figures/events/throw_escalation_event.png new file mode 100644 index 000000000..ff18477b6 Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/events/throw_escalation_event.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/spiff_message_throw.png b/SpiffWorkflow/doc/bpmn/figures/events/throw_message_event.png similarity index 100% rename from SpiffWorkflow/doc/bpmn/figures/spiff_message_throw.png rename to SpiffWorkflow/doc/bpmn/figures/events/throw_message_event.png diff --git a/SpiffWorkflow/doc/bpmn/figures/events/throw_signal_event.png b/SpiffWorkflow/doc/bpmn/figures/events/throw_signal_event.png new file mode 100644 index 000000000..f37754786 Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/events/throw_signal_event.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/events/timer_event.png b/SpiffWorkflow/doc/bpmn/figures/events/timer_event.png new file mode 100644 index 000000000..c72e5d055 Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/events/timer_event.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/events/transaction.png b/SpiffWorkflow/doc/bpmn/figures/events/transaction.png new file mode 100644 index 000000000..7d964581b Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/events/transaction.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/exclusive_gateway.png b/SpiffWorkflow/doc/bpmn/figures/exclusive_gateway.png deleted file mode 100644 index 21bea4fe7..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/exclusive_gateway.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/gateways/exclusive_gateway.png b/SpiffWorkflow/doc/bpmn/figures/gateways/exclusive_gateway.png new file mode 100644 index 000000000..eae37713b Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/gateways/exclusive_gateway.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/gateways/parallel_gateway.png b/SpiffWorkflow/doc/bpmn/figures/gateways/parallel_gateway.png new file mode 100644 index 000000000..fb727ea91 Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/gateways/parallel_gateway.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/lanes.png b/SpiffWorkflow/doc/bpmn/figures/lanes.png deleted file mode 100644 index c44dd80bc..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/lanes.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/multiinstance/availability_flow.png b/SpiffWorkflow/doc/bpmn/figures/multiinstance/availability_flow.png new file mode 100644 index 000000000..fda555a2b Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/multiinstance/availability_flow.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/multiinstance/call_activity_multi.png b/SpiffWorkflow/doc/bpmn/figures/multiinstance/call_activity_multi.png new file mode 100644 index 000000000..f970386a2 Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/multiinstance/call_activity_multi.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/multiinstance/loop_task.png b/SpiffWorkflow/doc/bpmn/figures/multiinstance/loop_task.png new file mode 100644 index 000000000..d9da23cfe Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/multiinstance/loop_task.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/multiinstance/multiinstance_task_configuration.png b/SpiffWorkflow/doc/bpmn/figures/multiinstance/multiinstance_task_configuration.png new file mode 100644 index 000000000..80293e2d1 Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/multiinstance/multiinstance_task_configuration.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/multiinstance_flow_configuration.png b/SpiffWorkflow/doc/bpmn/figures/multiinstance_flow_configuration.png deleted file mode 100644 index d8b96c2ae..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/multiinstance_flow_configuration.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/multiinstance_form_configuration.png b/SpiffWorkflow/doc/bpmn/figures/multiinstance_form_configuration.png deleted file mode 100644 index ea27d2462..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/multiinstance_form_configuration.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/organization/call_activity.png b/SpiffWorkflow/doc/bpmn/figures/organization/call_activity.png new file mode 100644 index 000000000..720240ce7 Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/organization/call_activity.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/organization/dmn_table_updated.png b/SpiffWorkflow/doc/bpmn/figures/organization/dmn_table_updated.png new file mode 100644 index 000000000..8d422ca85 Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/organization/dmn_table_updated.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/organization/lanes.png b/SpiffWorkflow/doc/bpmn/figures/organization/lanes.png new file mode 100644 index 000000000..8b243540f Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/organization/lanes.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/organization/top_level.png b/SpiffWorkflow/doc/bpmn/figures/organization/top_level.png new file mode 100644 index 000000000..9214a329e Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/organization/top_level.png differ diff --git a/SpiffWorkflow/doc/figures/ExclusiveGateway.png b/SpiffWorkflow/doc/bpmn/figures/overview/ExclusiveGateway.png similarity index 100% rename from SpiffWorkflow/doc/figures/ExclusiveGateway.png rename to SpiffWorkflow/doc/bpmn/figures/overview/ExclusiveGateway.png diff --git a/SpiffWorkflow/doc/figures/bpmnbook.jpg b/SpiffWorkflow/doc/bpmn/figures/overview/bpmnbook.jpg similarity index 100% rename from SpiffWorkflow/doc/figures/bpmnbook.jpg rename to SpiffWorkflow/doc/bpmn/figures/overview/bpmnbook.jpg diff --git a/SpiffWorkflow/doc/figures/events.png b/SpiffWorkflow/doc/bpmn/figures/overview/events.png similarity index 100% rename from SpiffWorkflow/doc/figures/events.png rename to SpiffWorkflow/doc/bpmn/figures/overview/events.png diff --git a/SpiffWorkflow/doc/figures/simplestworkflow.png b/SpiffWorkflow/doc/bpmn/figures/overview/simplestworkflow.png similarity index 100% rename from SpiffWorkflow/doc/figures/simplestworkflow.png rename to SpiffWorkflow/doc/bpmn/figures/overview/simplestworkflow.png diff --git a/SpiffWorkflow/doc/bpmn/figures/parallel_gateway.png b/SpiffWorkflow/doc/bpmn/figures/parallel_gateway.png deleted file mode 100644 index dab512bf4..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/parallel_gateway.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/script_engine/call_activity.png b/SpiffWorkflow/doc/bpmn/figures/script_engine/call_activity.png new file mode 100644 index 000000000..c92e8b382 Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/script_engine/call_activity.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/script_engine/top_level.png b/SpiffWorkflow/doc/bpmn/figures/script_engine/top_level.png new file mode 100644 index 000000000..76f69ba77 Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/script_engine/top_level.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/script_task.png b/SpiffWorkflow/doc/bpmn/figures/script_task.png deleted file mode 100644 index 769863730..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/script_task.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/signal_event.png b/SpiffWorkflow/doc/bpmn/figures/signal_event.png deleted file mode 100644 index 934e63da9..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/signal_event.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/tasks/business_rule_task.png b/SpiffWorkflow/doc/bpmn/figures/tasks/business_rule_task.png new file mode 100644 index 000000000..e941c262a Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/tasks/business_rule_task.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/tasks/dmn_table.png b/SpiffWorkflow/doc/bpmn/figures/tasks/dmn_table.png new file mode 100644 index 000000000..3ef453d7b Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/tasks/dmn_table.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/tasks/manual_task.png b/SpiffWorkflow/doc/bpmn/figures/tasks/manual_task.png new file mode 100644 index 000000000..ac41380b7 Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/tasks/manual_task.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/tasks/manual_task_instructions.png b/SpiffWorkflow/doc/bpmn/figures/tasks/manual_task_instructions.png new file mode 100644 index 000000000..efb00d6e3 Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/tasks/manual_task_instructions.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/tasks/script_task.png b/SpiffWorkflow/doc/bpmn/figures/tasks/script_task.png new file mode 100644 index 000000000..5a93df12a Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/tasks/script_task.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/tasks/user_task.png b/SpiffWorkflow/doc/bpmn/figures/tasks/user_task.png new file mode 100644 index 000000000..35ecf0786 Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/tasks/user_task.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/tasks/user_task_form.png b/SpiffWorkflow/doc/bpmn/figures/tasks/user_task_form.png new file mode 100644 index 000000000..b312d033c Binary files /dev/null and b/SpiffWorkflow/doc/bpmn/figures/tasks/user_task_form.png differ diff --git a/SpiffWorkflow/doc/bpmn/figures/throw_escalation_event.png b/SpiffWorkflow/doc/bpmn/figures/throw_escalation_event.png deleted file mode 100644 index 790fbbfb0..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/throw_escalation_event.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/throw_scalation_event.png b/SpiffWorkflow/doc/bpmn/figures/throw_scalation_event.png deleted file mode 100644 index b3aee96cb..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/throw_scalation_event.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/throw_signal_event.png b/SpiffWorkflow/doc/bpmn/figures/throw_signal_event.png deleted file mode 100644 index 6810f76aa..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/throw_signal_event.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/timer_event.png b/SpiffWorkflow/doc/bpmn/figures/timer_event.png deleted file mode 100644 index bd61b68d9..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/timer_event.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/top_level.png b/SpiffWorkflow/doc/bpmn/figures/top_level.png deleted file mode 100644 index da70d32a0..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/top_level.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/figures/transaction.png b/SpiffWorkflow/doc/bpmn/figures/transaction.png deleted file mode 100644 index 100c6f76e..000000000 Binary files a/SpiffWorkflow/doc/bpmn/figures/transaction.png and /dev/null differ diff --git a/SpiffWorkflow/doc/bpmn/gateways.rst b/SpiffWorkflow/doc/bpmn/gateways.rst index f715ae1b4..c3573ed21 100644 --- a/SpiffWorkflow/doc/bpmn/gateways.rst +++ b/SpiffWorkflow/doc/bpmn/gateways.rst @@ -13,14 +13,14 @@ method, and we updated our order total calculations to incorporate that cost. We'll be using the following files from `spiff-example-cli `_. -- `gateway_types `_ workflow -- `product_prices `_ DMN table -- `shipping_costs `_ DMN table +- `gateway_types `_ workflow +- `product_prices `_ DMN table +- `shipping_costs `_ DMN table Exclusive Gateway ^^^^^^^^^^^^^^^^^ -Exclusive gateways are used when exactly one alternative can be selected. +Exclusive Gateways are used when exactly one alternative can be selected. Suppose our products are T-shirts and we offer product C in several colors. After the user selects a product, we check to see it if is customizable. Our default @@ -28,7 +28,7 @@ branch will be 'Not Customizable', but we'll direct the user to a second form if they select 'C'; our condition for choosing this branch is a simple python expression. -.. figure:: figures/exclusive_gateway.png +.. figure:: figures/gateways/exclusive_gateway.png :scale: 30% :align: center @@ -44,7 +44,7 @@ Parallel Gateway leave it blank to avoid visual clutter. I've put a description of the gateway into the ID field instead. -Parallel gateways are used when the subsequent tasks do not need to be completed +Parallel Gateways are used when the subsequent tasks do not need to be completed in any particular order. The user can complete them in any sequence and the workflow will wait for all tasks to be finished before advancing. @@ -53,12 +53,25 @@ address first, but they'll need to complete both tasks before continuing. We don't need to do any particular configuration for this gateway type. -.. figure:: figures/parallel_gateway.png +.. figure:: figures/gateways/parallel_gateway.png :scale: 30% :align: center Parallel Gateway example +Inclusive Gateway +^^^^^^^^^^^^^^^^^ + +SpiffWorkflow also supports Inclusive Gateways, though we do not have an example of this gateway +type in this tutorial. Inclusive Gateways have conditions on outgoing flows like Exclusive Gateways, +but unlike Exclusive Gateways, multiple paths may be taken if more than one conition is met. + +Event-Based Gateway +^^^^^^^^^^^^^^^^^^^ + +SpiffWorkflow supports Event-Based Gateways, though we do not use them in this tutorial. Event-Based +gateways select an outgoing flow based on an event. We'll discuss events in the next section. + Running The Model ^^^^^^^^^^^^^^^^^ @@ -67,7 +80,7 @@ following command: .. code-block:: console - ./run.py -p order_product \ - -d bpmn/product_prices.dmn bpmn/shipping_costs.dmn \ - -b bpmn/gateway_types.bpmn + ./spiff-bpmn-runner.py -p order_product \ + -d bpmn/tutorial/product_prices.dmn bpmn/tutorial/shipping_costs.dmn \ + -b bpmn/tutorial/gateway_types.bpmn diff --git a/SpiffWorkflow/doc/bpmn/index.rst b/SpiffWorkflow/doc/bpmn/intro.rst similarity index 65% rename from SpiffWorkflow/doc/bpmn/index.rst rename to SpiffWorkflow/doc/bpmn/intro.rst index 8364834e5..0d4773137 100644 --- a/SpiffWorkflow/doc/bpmn/index.rst +++ b/SpiffWorkflow/doc/bpmn/intro.rst @@ -3,8 +3,7 @@ BPMN Workflows The basic idea of SpiffWorkflow is that you can use it to write an interpreter in Python that creates business applications from BPMN models. In this section, -we'll develop a model of an example process and as well as a -simple workflow runner. +we'll develop a model of a reasonably complex process and show how to run it. We expect that readers will fall into two general categories: @@ -12,8 +11,8 @@ We expect that readers will fall into two general categories: - Python developers who might not know much about BPMN This section of the documentation provides an example that (hopefully) serves -the needs of both groups. We will introduce the BPMN elements that SpiffWorkflow -supports and show how to build a simple workflow runner around them. +the needs of both groups. We will introduce some of the more common BPMN +elements and show how to build a simple workflow runner around them. SpiffWorkflow does heavy-lifting such as keeping track of task dependencies and states and providing the ability to serialize or deserialize a workflow that @@ -34,20 +33,22 @@ command: .. code-block:: console - ./run.py -p order_product \ - -d bpmn/{product_prices,shipping_costs}.dmn \ - -b bpmn/{multiinstance,call_activity_multi}.bpmn + ./spiff-bpmn-runner.py -c order_collaboration \ + -d bpmn/tutorial/{product_prices,shipping_costs}.dmn \ + -b bpmn/tutorial/{top_level_multi,call_activity_multi}.bpmn +.. sidebar:: BPMN Runner -For a full description of program options: + The example app provides a utility for running BPMN Diagrams from the command + line that will allow you to introspect a bit on a running process. You + can see the options available by running: -.. code-block:: console - - ./run.py --help + ./spiff-bpmn-runner.py --help The code in the workflow runner and the models in the bpmn directory of the repository will be discussed in the remainder of this tutorial. + Supported BPMN Elements ----------------------- @@ -58,8 +59,8 @@ Supported BPMN Elements gateways organization events + data multiinstance - spiff-extensions Putting it All Together ----------------------- @@ -76,3 +77,27 @@ Features in More Depth :maxdepth: 2 advanced + +Custom Task Specs +----------------- + +.. toctree:: + :maxdepth: 2 + + custom_task_spec + +Exceptions +---------- + +.. toctree:: + :maxdepth: 2 + + errors + +Camunda Editor Support +---------------------- + +.. toctree:: + :maxdepth: 2 + + camunda/support diff --git a/SpiffWorkflow/doc/bpmn/multiinstance.rst b/SpiffWorkflow/doc/bpmn/multiinstance.rst index 03e36f280..8dcfebf7d 100644 --- a/SpiffWorkflow/doc/bpmn/multiinstance.rst +++ b/SpiffWorkflow/doc/bpmn/multiinstance.rst @@ -6,107 +6,138 @@ BPMN Model We'll be using the following files from `spiff-example-cli `_. -- `multiinstance `_ workflow -- `call activity multi `_ workflow -- `product_prices `_ DMN table -- `shipping_costs `_ DMN table +- `multiinstance `_ workflow +- `call activity multi `_ workflow +- `product_prices `_ DMN table +- `shipping_costs `_ DMN table + +Loop Task +^^^^^^^^^ Suppose we want our customer to be able to select more than one product. -If we knew how many products they would select at the beginning of the workflow, we could -configure 'Select and Customize Product' as a Sequential MultiInstance Task. We would -specify the name of the collection and each iteration of the task would add a new item -to it. +We'll run our 'Select and Customize Product' Call Activity as a Loop Task. -Since we can't know in advance how many products the order, we'll need to modify that -workflow to ask them whether they want to continue shopping and maintain their product -selections in a collection. +First we'll update the Call Activity's model to ask the customer if they would like to continue shopping. -.. figure:: figures/call_activity_multi.png +.. figure:: figures/multiinstance/call_activity_multi.png :scale: 30% :align: center Selecting more than one product -We'll also need to update our element documentation to display all products. +We've also added a *postScript* to the user task. Spiffworkflow provides extensions that allow scripts to be +run before and after tasks. It is often the case that data needs to be manipulated before and after a task. +We could add regular Script Tasks before and after, but diagrams quickly become cluttered with scripts, and +these extensions are intended to alleviate that. -.. figure:: figures/documentation_multi.png +We use a *postScript* to add the current product to a list of products. + +.. code:: python + + products.append({ + 'product_name': product_name, + 'product_quantity': product_quantity, + 'product_color': product_color, + 'product_size': product_size, + 'product_style': product_style, + 'product_price': product_price, + }) + +We'll use a *preScript* on the first User Task (Select Product and Quantity) to initialize these variables to +:code:`None` each time we execute the task. + +Loop Tasks run either a specified number of times or until a completion condition is met. Since we can't +know in advance how many products the customer will select, we'll add :code:`continue_shopping == 'Y'` as a +completion condition. We'll re-run this Call Activity as long as the customer indicates they want to choose +another product. We'll also set up the list of products that we plan on appending to. + +We also added a postscript to this activity to delete the customization values so that we won't have to +look at them for the remainder of the workflow. + +.. figure:: figures/multiinstance/loop_task.png :scale: 30% :align: center - Updated Documentation for 'Review Order' + Call Activity with Loop -.. note:: +We also needed to update our Script Task and the Instructions of the Review Order Task to handle an array +of products rather than a single product. - Note that we are using a dot instead of the typical python dictionary access to obtain - the values. Spiff automatically generates such a representation, which simplifies creating the - documentation strings; however regular Python syntax will work as well. +Here is our new script + +.. code:: python + + order_total = sum([ p['product_quantity'] * p['product_price'] for p in products ]) + shipping_cost + +And our order summary + +.. code:: python + + Order Summary + {% for product in products %} + {{ product['product_name'] }} + Quantity: {{ product['product_quantity'] }} + Price: {{ product['product_price'] }} + {% endfor %} + Shipping Cost: {{ shipping_cost }} + Order Total: {{ order_total }} Parallel MultiInstance ^^^^^^^^^^^^^^^^^^^^^^ -We'll also update our 'Retrieve Product' task and 'Product Not Available' flows to +We'll also update our 'Retrieve Product' Task and 'Product Not Available' flows to accommodate multiple products. We can use a Parallel MultiInstance for this, since it does not matter what order our Employee retrieves the products in. -.. figure:: figures/multiinstance_task_configuration.png +.. figure:: figures/multiinstance/multiinstance_task_configuration.png :scale: 30% :align: center - MultiInstance task configuration + MultiInstance Task configuration -Spiff will generate a task for each of the items in the collection. Because of the way -SpiffWorkflow manages the data for these tasks, the collection MUST be a dictionary. +We've specified :code:`products` as our Input Collection and :code:`product` as our Input Item. The +Input Collection should be an existing collection. We'll create a task instance for each element of +the collection, and copy the value into the Input Item; this is how we'll access the data of the +element. -Each value in the dictionary will be copied into a variable with the name specified in -the 'Element Variable' field, so you'll need to specify this as well. +.. :code:: -.. figure:: figures/multiinstance_form_configuration.png - :scale: 30% + Item: {{product['product_quantity']}} of {{product['product_name']}} + +We also specified :code:`availability` as our Output Collection. Since this variable does not exist, +SpiffWorkflow will automatically create it. You can use an existing variable as an Output Collection; +in this case, its contents will be updated with new values. The Output Item will be copied out of the +child task into the Output Collection. + +The 'Retrieve Product' task creates :code:`product_available` from the form input. + +Since our input is a list, our output will also be a list. It is possible to generate different output +types if you create the output collections before referring to them. + +We have to update our gateway condition to handle the list: + +.. figure:: figures/multiinstance/availability_flow.png + :scale: 60% :align: center - MultiInstance form configuration + Gateway Condition -We'll also need to update the form field id so that the results will be added to the -item of the collection rather than the top level of the task data. This is where the -'Element Variable' field comes in: we'll need to change `product_available` to -`product.product_available`, because we set up `product` as our reference to the -current item. - -.. figure:: figures/multiinstance_flow_configuration.png - :scale: 30% - :align: center - - Product available flow configuration - -Finally, we'll need to update our 'No' flow to check all items in the collection for -availability. - -.. note:: - - In our form configuration, we used `product.product_available` but when we reference - it in the flow, we use the standard python dictionary syntax. We can't use that - notation in form fields, so in this case we need to use SpiffWorkflow's dot notation - conversion. Sequential MultiInstance ^^^^^^^^^^^^^^^^^^^^^^^^ -SpiffWorkflow also supports Sequential MultiInstance Tasks for previously defined -collections, or if the loopCardinality is known in advance, although we have not added an -example of this to our workflow. - -For more information about MultiInstance Tasks and SpiffWorkflow, see :doc:`/bpmn/advanced`. +SpiffWorkflow also supports Sequential MultiInstance Tasks for collections, or if the loopCardinality +is known in advance, although we have not added an example of this to our workflow. Their configuraiton +is almost idenitcal to the configuration for Parallel MultiInstance Tasks. Running The Model ^^^^^^^^^^^^^^^^^ -If you have set up our example repository, this model can be run with the -following command: +If you have set up our example repository, this model can be run with the following command: .. code-block:: console - ./run.py -p order_product \ - -d bpmn/product_prices.dmn bpmn/shipping_costs.dmn \ - -b bpmn/multiinstance.bpmn bpmn/call_activity_multi.bpmn - + ./spiff-bpmn-runner.py -p order_product \ + -d bpmn/tutorial/product_prices.dmn bpmn/tutorial/shipping_costs.dmn \ + -b bpmn/tutorial/top_level_multi.bpmn bpmn/tutorial/call_activity_multi.bpmn diff --git a/SpiffWorkflow/doc/bpmn/organization.rst b/SpiffWorkflow/doc/bpmn/organization.rst index ad78be86f..f2c038322 100644 --- a/SpiffWorkflow/doc/bpmn/organization.rst +++ b/SpiffWorkflow/doc/bpmn/organization.rst @@ -6,11 +6,11 @@ BPMN Model We'll be using the following files from `spiff-example-cli `_. -- `lanes `_ workflow -- `top_level `_ workflow -- `call_activity `_ workflow -- `product_prices `_ DMN table -- `shipping_costs `_ DMN table +- `lanes `_ workflow +- `top_level `_ workflow +- `call_activity `_ workflow +- `product_prices `_ DMN table +- `shipping_costs `_ DMN table Lanes ^^^^^ @@ -18,13 +18,13 @@ Lanes Lanes are a method in BPMN to distinguish roles for the workflow and who is responsible for which actions. In some cases this will be different business units, and in some cases this will be different individuals - it really depends -on the nature of the workflow. Within a BPMN editor, this is done by choosing the +on the nature of the workflow. Within the BPMN editor, this is done by choosing the 'Create pool/participant' option from the toolbar on the left hand side. We'll modify our workflow to get the customer's payment information and send it to an employee who will charge the customer and fulfill the order. -.. figure:: figures/lanes.png +.. figure:: figures/organization/lanes.png :scale: 30% :align: center @@ -34,27 +34,23 @@ To run this workflow .. code-block:: console - ./run.py -p order_product \ - -d bpmn/product_prices.dmn bpmn/shipping_costs.dmn \ - -b bpmn/lanes.bpmn + ./spiff-bpmn-runner.py -p order_product \ + -d bpmn/tutorial/product_prices.dmn bpmn/tutorial/shipping_costs.dmn \ + -b bpmn/tutorial/lanes.bpmn For a simple code example of displaying a tasks lane, see `Handling Lanes`_ Subprocesses ^^^^^^^^^^^^ -In general, subprocesses are a way of grouping work into smaller units. This, in -theory, will help us to re-use sections of business logic, but it will also allow -us to treat groups of work as a unit. +Subprocesses allow us to conceptualize a group of tasks as a unit by creating a +mini-workflow inside a task. Subprocess Tasks come in two different flavors: expanded +or collapsed. The difference between the two types is visual rather than functional. -Subprocesses come in two different flavors. In this workflow we see an Expanded -Subprocess. Unfortunately, we can't collapse an expanded subprocess within BPMN.js, -so expanded subprocesses are mainly useful for conceptualizing a group of tasks as -a unit. - -It also possible to refer to external subprocesses via a Call Activity Task. This -allows us to 'call' a separate workflow in a different file by referencing the ID of -the called workflow, which can simplify business logic and make it re-usable. +It also possible to refer to external processes via a Call Activity Task. This +allows us to 'call' a separate Process (which might be stored independently of the +Process we're implementing) by referencing the ID of the called Process, which can simplify +business logic and make it re-usable. We'll expand 'Fulfill Order' into sub tasks -- retrieving the product and shipping the order -- and create an Expanded Subprocess. @@ -62,11 +58,11 @@ the order -- and create an Expanded Subprocess. We'll also expand our selection of products, adding several new products and the ability to customize certain products by size and style in addition to color. -.. figure:: figures/dmn_table_updated.png - :scale: 30% +.. figure:: figures/organization/dmn_table_updated.png + :scale: 60% :align: center - Updated Product List + Updated product list .. note:: @@ -75,33 +71,32 @@ to customize certain products by size and style in addition to color. the option of documenting the decisions contained in the table. Since adding gateways for navigating the new options will add a certain amount of -clutter to our diagram, we'll create a separate workflow around selecting and -customizing products and refer to that in our main workflow. +clutter to our diagram, we'll create a separate workflow for selecting and customizing +products and refer to that in our main workflow. -.. figure:: figures/call_activity.png +.. figure:: figures/organization/call_activity.png :scale: 30% :align: center Subworkflow for product selection -When configuring the subworkflow, we need to make sure the 'CallActivity Type' of the -parent workflow is 'BPMN' and the 'Called Element' matches the ID we assigned in the -subworkflow. +We need to make sure the 'Called Element' matches the ID we assigned in the called Process. -.. figure:: figures/top_level.png +.. figure:: figures/organization/top_level.png :scale: 30% :align: center Parent workflow + Running the Model ^^^^^^^^^^^^^^^^^ .. code-block:: console - ./run.py -p order_product \ - -d bpmn/product_prices.dmn bpmn/shipping_costs.dmn \ - -b bpmn/top_level.bpmn bpmn/call_activity.bpmn + ./spiff-bpmn-runner.py -p order_product \ + -d bpmn/tutorial/product_prices.dmn bpmn/tutorial/shipping_costs.dmn \ + -b bpmn/tutorial/top_level.bpmn bpmn/tutorial/call_activity.bpmn Example Application Code ------------------------ @@ -115,14 +110,16 @@ our sample application, we'll simply display which lane a task belongs to. .. code:: python - if hasattr(task.task_spec, 'lane') and task.task_spec.lane is not None: - lane = f'[{task.task_spec.lane}]' - else: - lane = '' + def get_task_description(self, task, include_state=True): -The tasks lane can be obtained from :code:`task.task_spec.lane`. Not all tasks -will have a :code:`lane` attribute, so we need to check to make sure it exists -before attempting to access it (this is true for many task attributes). + task_spec = task.task_spec + lane = f'{task_spec.lane}' if task_spec.lane is not None else '-' + name = task_spec.bpmn_name if task_spec.bpmn_name is not None else '-' + description = task_spec.description if task_spec.description is not None else 'Task' + state = f'{task.get_state_name()}' if include_state else '' + return f'[{lane}] {name} ({description}: {task_spec.bpmn_id}) {state}' -See the Filtering Tasks Section of :doc:`advanced` more information -about working with lanes in Spiff. +The tasks lane can be obtained from :code:`task.task_spec.lane`, which will be :code:`None` +if the task is not part of a lane. + +See the Filtering Tasks Section of :doc:`advanced` more information about working with lanes in Spiff. diff --git a/SpiffWorkflow/doc/intro.rst b/SpiffWorkflow/doc/bpmn/overview.rst similarity index 54% rename from SpiffWorkflow/doc/intro.rst rename to SpiffWorkflow/doc/bpmn/overview.rst index 760f5b67d..2a1999b1d 100644 --- a/SpiffWorkflow/doc/intro.rst +++ b/SpiffWorkflow/doc/bpmn/overview.rst @@ -11,39 +11,41 @@ BPMN and SpiffWorkflow resources. We have used the `books by Bruce Silver `_ as a guide for our BPMN modeling. - .. image:: figures/bpmnbook.jpg + .. image:: figures/overview/bpmnbook.jpg :align: center -Business Process Model and Notation (BPMN) is a diagramming language for -specifying business processes. BPMN links the realms of business and IT, and -creates a common process language that can be shared between the two. +Business Process Model and Notation (BPMN) is a diagramming language for specifying business +processes. BPMN links the realms of business and IT, and creates a common process language that +can be shared between the two. -BPMN describes details of process behaviors efficiently in a diagram. The -meaning is precise enough to describe the technical details that control -process execution in an automation engine. SpiffWorkflow allows you to create -code to directly execute a BPMN diagram. +BPMN describes details of process behaviors efficiently in a diagram. The meaning is precise enough +to describe the technical details that control process execution in an automation engine. +SpiffWorkflow allows you to create code to directly execute a BPMN diagram. -When using SpiffWorkflow, a client can manipulate the BPMN diagram and still -have their product work without a need for you to edit the Python code, -improving response and turnaround time. - -Today, nearly every process modeling tool supports BPMN in some fashion making -it a great tool to learn and use. - -To use SpiffWorkflow, you need at least a basic understanding of BPMN. -This page offers a brief overview. There are many resources for additional -information about BPMN. +When using SpiffWorkflow, a client can manipulate the BPMN diagram and still have their product work +without a need for you to edit the Python code, improving response and turnaround time. .. sidebar:: BPMN Modelers - There are a number of modelers in existence, and any BPMN compliant modeler should work. - SpiffWorkflow has some basic support for the free Camunda modeler, to use it's form building - capabilities, but we intend to encapsulate this support in an extension module and remove - it from the core library eventually. It does help for making some examples and demonstrating - how one might implement user tasks in an online environment. -In these examples and throughout the documentation we use the -`BPMN.js `_ BPMN Modeler. + Currently the best way to build BPMN diagrams is through our SpiffArena project + which provides a custom BPMN Modeler, along with ways to test and run BPMN diagrams + from within a web browser. Please see our `getting started guide `_ + for more information. + + It is also possible to use version 7 of the Camunda Modeler to create BPMN diagrams. + However, be cautious of the properies panel settings, as many of these settings are + not a part of the BPMN Standard, and are not handled in the same way within SpiffWorkflow. + You can download the Camunda Modeler from `Camunda `_. + +Today, nearly every process modeling tool supports BPMN in some fashion making it a great tool to +learn and use. This page provides a brief overview, and the following section provides a more +in-depth look. There are many resources for additional information about BPMN. + +Most of the examples in this guide have been created with +`our modeler `_, which is based on +`bpmn.js `_. + A Simple Workflow @@ -56,7 +58,7 @@ by a single thick border circle. The following example also has one task, represented by the rectangle with curved corners. -.. figure:: figures/simplestworkflow.png +.. figure:: figures/overview/simplestworkflow.png :scale: 25% :align: center @@ -70,7 +72,7 @@ the tail of a sequence flow completes, the node at the arrowhead is enabled to s A More Complicated Workflow --------------------------- -.. figure:: figures/ExclusiveGateway.png +.. figure:: figures/overview/ExclusiveGateway.png :scale: 25% :align: center @@ -85,27 +87,36 @@ the other based on some data condition. BPMN has other gateway types. The important point is that we can use a gateway to add a branch in the workflow **without** creating an explicit branch in our Python code. -Events +An Even More Complicated Workflow ------ +BPMN is a rich language that can describe many different types of processes. In +the following pages we'll cover lanes (a way to distribute work across different +roles) events (a way to handle asynchronous events), multi-instance tasks (that +can be executed many times in parallel or in sequence) and decomposition (the +many ways you can interconnect diagrams to build larger more complex processes) +We are just scratching the surface. For now let's take one more step and look +at what Events make possible. +Events +^^^^^^^ In the above simple workflows, all of the transitions are deterministic and we have direct connections between tasks. We need to handle the cases where an event -may or may not happen and link these events in different parts of the workflow. +may or may not happen, and link these events in different parts of the workflow or +across different workflows. -BPMN has a comprehensive suite of event elements that can used for this purpose. -SpiffWorkflow does not support every single BPMN event type, but it can handle -many of them. +BPMN has a comprehensive suite of event elements. SpiffWorkflow does not support +every single BPMN event type, but it can handle many of them. -.. figure:: figures/events.png +.. figure:: figures/overview/events.png :scale: 25% :align: center A workflow containing events -We've already seen plain Start and End Events. BPMN also include the concepts +We've already seen plain Start and End Events. BPMN also includes the concept of Intermediate Events (standalone events that may be Throwing or Catching) as well -as Boundary Events (which can only be Caught). +as Boundary Events (which are exclusively Catching). All Start Events are inherently Catching Events (a workflow can be initiated if a particular event occurs) and all End Events are Throwing Events (they can convey diff --git a/SpiffWorkflow/doc/bpmn/spiff-extensions.rst b/SpiffWorkflow/doc/bpmn/spiff-extensions.rst deleted file mode 100644 index 00cb395b1..000000000 --- a/SpiffWorkflow/doc/bpmn/spiff-extensions.rst +++ /dev/null @@ -1,112 +0,0 @@ -Spiff Extensions -================ - -BPMN Model ----------- - -We'll be using the following files from `spiff-example-cli `_. - -- `bpmn-spiff/events `_ workflow -- `bpmn-spiff/call activity `_ workflow -- `product_prices `_ DMN table -- `shipping_costs `_ DMN table - -We'll also be using the `run-spiff.py `_ script -instead of the `run.py `_ script - -Camunda's BPMN editor does not handle data objects in the expected way. You can create data object -references, but there is no way to re-use data objects. - -It also does not support Message Correlations, and the interface for generating a message payload doesn't work -well in a Python environment. - -We have extended BPMN.js to correct some of these issues. The examples in this section were created using our -custom BPMN editor, `bpmn-js-spiffworkflow `_. - -Data Objects -^^^^^^^^^^^^ - - Data objects exist at a process level and are not visible in the diagram, but when you create a data object - reference, you can choose what data object it points to. - -.. figure:: figures/data_object_configuration.png - :scale: 50% - :align: center - - Configuring a data object reference - -When a data output association (a line) is drawn from a task to a data object reference, the value is copied -from the task data to the workflow data and removed from the task. If a data input association is created from -a data object reference, the value is temporarily copied into the task data while the task is being executed, -and immediate removed afterwards. - -This allows sensitive data to be removed from individual tasks (in our example, the customer's credit card -number). It can also be used to prevent large objects from being repeatedly copied from task to task. - -Multiple data object references can point to the same underlying data. In our example, we use to references -to the same data object to pass the credit card info to both tasks that require it. On the right panel, we can -see that only one data object exists in the process. - -.. figure:: figures/data_objects.png - :scale: 30% - :align: center - - Data objects in a process - -If you step through this workflow, you'll see that the card number is not contained in the task data after -the 'Enter Payment Info' has been completed. - -Configuring Messages -^^^^^^^^^^^^^^^^^^^^ - -Messages are handled slightly differently in Spiff Message Events. On a Message Throw Event or Send Task, -we define a payload, which is simply a bit of python code that will be evaluated against the task data and -sent along with the message. In the corresponding Message Catch Event or Receive Task, we define a -variable name where we'll store the result. - -Spiff Messages can also optionally use correlation keys. The correlation key is an expression or set of -expressions that are evaluated against a message payload to create an additional identifier for associating -messages with processes. - -In our example, it is possible that multiple QA processes could be started (the timer event will fire every -minute until the order fulfillment process is complete). In this case, the message name is insufficient, as -there will be multiple processes that can accept messages based on the name. - -.. figure:: figures/correlation.png - :scale: 50% - :align: center - - Defining a correlation key - -We use the timestamp of the message creation as a unique key that can be used to distinguish between multiple -QA processes. - -.. figure:: figures/spiff_message_throw.png - :scale: 50% - :align: center - - Configuring a message throw event - -When we receive the event, we assign the payload to :code:`order_info`. - -.. figure:: figures/spiff_message_catch.png - :scale: 50% - :align: center - - Configuring a message catch event - -The correlation is visible on both the Throw and Catch Events, but it is associated with the message rather -than the tasks themselves; if you update the expression on either event, the changes will appear in both places. - -Running The Model -^^^^^^^^^^^^^^^^^ - -If you have set up our example repository, this model can be run with the -following command: - -.. code-block:: console - - ./run-spiff.py -p order_product \ - -d bpmn/product_prices.dmn bpmn/shipping_costs.dmn \ - -b bpmn-spiffevents.bpmn bpmn-spiff/call_activity.bpmn - diff --git a/SpiffWorkflow/doc/bpmn/synthesis.rst b/SpiffWorkflow/doc/bpmn/synthesis.rst index cb676244a..41d89485a 100644 --- a/SpiffWorkflow/doc/bpmn/synthesis.rst +++ b/SpiffWorkflow/doc/bpmn/synthesis.rst @@ -4,237 +4,318 @@ Putting it All Together In this section we'll be discussing the overall structure of the workflow runner we developed in `spiff-example-cli `_. -Our example application contains two different workflow runners, one that uses tasks with -Camunda extensions -(`run.py `_) and one -that uses tasks with Spiff extensions -(`run-spiff.py `_). +Our example application contains two different workflow runners, one that uses tasks with with Spiff extensions +(`spiff-bpmn-runner.py `_) +and one that uses the **deprecated** Camunda extensions +(`camunda-bpmn-runner.py `_). -Most of the workflow operations will not change, so shared functions are defined in -`utils.py `_. +The primary differences between the two are in handling User and MultiInstance Tasks. We have some documentation +about how we interpret Camunda forms in :doc:`camunda/tasks`. That particular page comes from an earlier version of +our documentation, and `camunda-bpmn-runner.py` can run workflows with these tasks. However, we are not actively +maintaining the :code:`camunda` package, and it should be considered deprecated. -The primary difference is handling user tasks. Spiff User Tasks define an extensions -property that stores a filename containing a JSON schema used to define a web form. We -use `react-jsonschema-form `_ -to define our forms. This doesn't necessarily make a lot of sense in terms of a command -line UI, so we'll focus on the Camunda workflow runner in this document. +Base Application Runner +----------------------- -Loading a Workflow -------------------- +The core functions your application will have to accomodate are -The :code:`CamundaParser` extends the base :code:`BpmnParser`, adding functionality for -parsing forms defined in Camunda User Tasks and decision tables defined in Camunda -Business Rule Tasks. (There is a similar :code:`SpiffBpmnParser` used by the alternate -runner.) +* parsing workflows +* serializing workflows +* running workflows -We create the parser and use it to load our workflow. +Task specs define how tasks are executed, and creating the task specs depends on a parser which initializes a spec of +the appropriate class. And of course serialization is also heavily dependent on the same information needed to create +the instance. To that end, our BPMN runner requires that you provide a parser and serializer; it can't operate unless +it knows what to do with each task spec it runs across. + +Here is the initialization for the :code:`runner.SimpleBpmnRunner` class that is used by both scripts. .. code:: python - parser = CamundaParser() - wf = parse_workflow(parser, args.process, args.bpmn, args.dmn) + def __init__(self, parser, serializer, script_engine=None, handlers=None): -Our workflow parser looks like this; + self.parser = parser + self.serializer = serializer + self.script_engine = script_engine + self.handlers = handlers or {} + self.workflow = None + +If you read the introduction to BPMN, you'll remember that there's a Script Task; the script engine executes scripts +against the task data and updates it. Gateway conditions are also evaluated against the same context by the engine. + +SpiffWorkflow provides a default scripting environment that is suitable for simple applications, but a serious application +will probably need to extend (or restrict) it in some way. See :doc:`advanced` for a few examples. Therefore, we have the +ability to optionally pass one in. + +The `handlers` argument allows us to let our application know what to do with specific task spec types. It's a mapping +of task spec class to its handler. Most task specs won't need handlers outside of how SpiffWorkflow executes them +(that's probably why you are using this library). You'll only have to be concerned with the task spec types that +require human interaction; Spiff will not handle those for you. In your application, these will probably be built into +it and you won't need to pass anything in. + +However, here we're trying to build something flexible enough that it can at least deal with two completely different +mechanisms for handling User Tasks, and provide a means for you to experiment with this application. + + +Parsing Workflows +----------------- + +Here is the method we use to parse the workflows; .. code:: python - def parse_workflow(parser, process, bpmn_files, dmn_files, load_all=True): - parser.add_bpmn_files(bpmn_files) + def parse(self, name, bpmn_files, dmn_files=None, collaboration=False): + + self.parser.add_bpmn_files(bpmn_files) if dmn_files: - parser.add_dmn_files(dmn_files) - top_level = parser.get_spec(process) - if load_all: - subprocesses = parser.find_all_specs() + self.parser.add_dmn_files(dmn_files) + + if collaboration: + top_level, subprocesses = self.parser.get_collaboration(name) else: - subprocesses = parser.get_subprocess_specs(process) - return BpmnWorkflow(top_level, subprocesses, script_engine=CustomScriptEngine) + top_level = self.parser.get_spec(name) + subprocesses = self.parser.get_subprocess_specs(name) + self.workflow = BpmnWorkflow(top_level, subprocesses, script_engine=self.script_engine) -We'll obtain the workflow specification from the parser for the top level process -using :code:`parser.get_spec()`. +We add the BPMN and DMN files to the parser and use :code:`parser.get_spec` to create a workflow spec for a process +model. -We have two options for finding subprocess specs. The method :code:`parser.find_all_specs()` -will create specs for all executable processes found in every file supplied. The method -:code:`parser.get_subprocess_specs(process)` will create specs only for processes used by -the specified process. Both search recursively for subprocesses; the only difference is -the latter method limits the search start to the specified process. +SpiffWorkflow needs at least one spec to create a workflow; this will be created from the name of the process passed +into the method. It also needs specs for any subprocesses or call activities. The method +:code:`parser.get_subprocess_specs` will search recursively through a starting spec and collect specs for all +referenced resources. -Our examples are pretty simple, and we're not loading any extraneous stuff, so we'll -just always load everything. If your entire workflow is contained in your top-level -process, you can omit the :code:`subprocess` argument, but if your workflow contains -call activities, you'll need to use one of these methods to find the models for any -called processes. +It is possible to have two processes defined in a single model, via a Collaboration. In this case, there is no "top +level spec". We can use :code:`self.parser.get_collaboration` to handle this case. -We also provide an enhanced script engine to our workflow. More information about how and -why you might want to do this is covered in :doc:`advanced`. The :code:`script_engine` -argument is optional and the default will be used if none is supplied. +.. note:: -We return :code:`BpmnWorkflow` that runs our top-level workflow and contains specs for any -subprocesses defined by that workflow. + The only required argument to :code:`BpmnWorkflow` is a single workflow spec, in this case `top_level`. The + parser returns an empty dict if no subprocesses are present, but it is not required to pass this in. If there + are subprocess present, `subprocess_specs` will be a mapping of process ID to :code:`BpmnWorkflowSpec`. + +In :code:`simple_bpmn_runner.py` we create the parser like this: + +.. code:: python + + from SpiffWorkflow.spiff.parser.process import SpiffBpmnParser, BpmnValidator + parser = SpiffBpmnParser(validator=BpmnValidator()) + +The validator is an optional argument, which can be used to validate the BPMN files passed in. The :code:`BpmnValidator` +in the :code:`spiff` package is configured to validate against the BPMN 2.0 spec and our spec describing our own +extensions. + +The parser we imported is pre-configured to create task specs that know about Spiff extensions. + +There are parsers in both the :code:`bpmn` and :code:`camunda` packages that can be similarly imported. There is a +validator that uses only the BPMN 2.0 spec in the :code:`bpmn` package (but no similar validator for Camunda). + +It is possible to override particular task specs for specific BPMN Task types. We'll cover an example of this in +:doc:`advanced`. + +Serializing Workflows +--------------------- + +In addition to the pre-configured parser, each package has a pre-configured serializer. + +.. code:: python + + from SpiffWorkflow.spiff.serializer.config import SPIFF_SPEC_CONFIG + from runner.product_info import registry + wf_spec_converter = BpmnWorkflowSerializer.configure_workflow_spec_converter(SPIFF_SPEC_CONFIG) + serializer = BpmnWorkflowSerializer(wf_spec_converter, registry) + +The serializer has two components: + +* the `workflow_spec_converter`, which knows about objects inside SpiffWorkflow +* the `registry`, which can tell SpiffWorkflow how to handle arbitrary data from your scripting environment + (required only if you have non-JSON-serializable data there). + +We discuss the creation and use of `registry` in :doc:`advanced` so we'll ignore it for now. + +`SPIFF_SPEC_CONFIG` has serialization methods for each of the task specs in its parser and we can create a +converter from it directly and pass it into our serializer. + +Here is our deserialization code: + +.. code:: python + + def restore(self, filename): + with open(filename) as fh: + self.workflow = self.serializer.deserialize_json(fh.read()) + if self.script_engine is not None: + self.workflow.script_engine = self.script_engine + +We'll just pass the contents of the file to the serializer and it will restore the workflow. The scripting environment +was not serialized, so we have to make sure we reset it. + +And here is our serialization code: + +.. code:: python + + def dump(self): + filename = input('Enter filename: ') + with open(filename, 'w') as fh: + dct = self.serializer.workflow_to_dict(self.workflow) + dct[self.serializer.VERSION_KEY] = self.serializer.VERSION + fh.write(json.dumps(dct, indent=2, separators=[', ', ': '])) + +The serializer has a companion method :code:`serialize_json` but we're bypassing that here so that we can make the +output readable. + +The heart of the serialization process actually happens in :code:`workflow_to_dict`. This method returns a +dictionary representation of the workflow that contains only JSON-serializable items. All :code:`serialize_json` +does is add a serializer version and call :code:`json.dumps` on the returned dict. If you are developing a serious +application, it is unlikely you want to store the entire workflow as a string, so you should be aware that this method +exists. + +The serializer is fairly complex: not only does it need to handle SpiffWorkflow's own internal objects that it +knows about, it needs to handle arbitrary Python objects in the scripting environment. The serializer is covered in +more depth in :doc:`advanced`. Defining Task Handlers ---------------------- -In :code:`run.py`, we define the function :code:`complete_user_task`. This has code specific -to Camunda User Task specs (in :code:`run-spiff.py`, we do something different). +In :code:`spiff-bpmn-runner.py`, we also define the functions :code:`complete_user_task`. and +:code:`complete_manual_task`. -We also import the shared function :code:`complete_manual_task` for handling Manual -Tasks as there is no difference. +We went over these handlers in :doc:`tasks`, so we won't delve into them here. We create a mapping of task type to handler, which we'll pass to our workflow runner. .. code:: python handlers = { - ManualTask: complete_manual_task, UserTask: complete_user_task, + ManualTask: complete_manual_task, + NoneTask: complete_manual_task, } -This might not be a step you would need to do in an application you build, since -you would likely have only one set of task specs that need to be parsed, handled, and -serialized; however, our `run` method is an awful lot of code to maintain in two separate -files. +In SpiffWorkflow the :code:`NoneTask` (which corresponds to the `bpmn:task` is treated as a human task, and therefore +has no built in way of handling them. Here we treat them as if they were Manual Tasks. -Running a Workflow ------------------- +Running Workflows +----------------- -This is our application's :code:`run` method. - -We pass our workflow, the task handlers, a serializer (creating a serializer is covered in -more depth in :doc:`advanced`). - -The :code:`step` argument is a boolean that indicates whether we want the option of seeing -a more detailed representation of the state at each step, which we'll discuss in the -section following this one. The :code:`display_types` argument controls what types of -tasks should be included in a detailed list when stepping through a process. +Our application's :code:`run_workflow` method takes one argument: `step` is a boolean that lets the runner know +if if should stop and present the menu at every step (if :code:`True`) or only where there are human tasks to +complete. .. code:: python - def run(workflow, task_handlers, serializer, step, display_types): + def run_workflow(self, step=False): - workflow.do_engine_steps() + while not self.workflow.is_completed(): - while not workflow.is_completed(): + if not step: + self.advance() - ready_tasks = workflow.get_ready_user_tasks() - options = { } - print() - for idx, task in enumerate(ready_tasks): - option = format_task(task, False) - options[str(idx + 1)] = task - print(f'{idx + 1}. {option}') + tasks = self.workflow.get_tasks(TaskState.READY|TaskState.WAITING) + runnable = [t for t in tasks if t.state == TaskState.READY] + human_tasks = [t for t in runnable if t.task_spec.manual] + current_tasks = human_tasks if not step else runnable - selected = None - while selected not in options and selected not in ['', 'D', 'd']: - selected = input('Select task to complete, enter to wait, or D to dump the workflow state: ') + self.list_tasks(tasks, 'Ready and Waiting Tasks') + if len(current_tasks) > 0: + action = self.show_workflow_options(current_tasks) + else: + action = None + if len(tasks) > 0: + input("\nPress any key to update task list") - if selected.lower() == 'd': - filename = input('Enter filename: ') - state = BpmnSerializer().serialize_workflow(workflow, include_spec=True) - with open(filename, 'w') as dump: - dump.write(state) - elif selected != '': - next_task = options[selected] - handler = task_handlers.get(type(next_task.task_spec)) +In the code above we first get the list of all `READY` or `WAITING` tasks; these are the currently active tasks. +`READY` tasks can be run, and `WAITING` tasks may change to `READY` (see :doc:`../concepts` for a discussion of task +states). We aren't going to do anything with the `WAITING` tasks except display them. + +We can further filter our runnable tasks on the :code:`task_spec.manual` attribute. If we're stepping though the +workflow, we'll present the entire list; otherwise only the human tasks. There are actually many points where no +human tasks are available to execute; the :code:`advance` method runs the other runnable tasks if we've opted to +skip displaying them; we'll look at that method after this one. + +There may also be points where there are no runnable tasks at all (for example, if the entire process is waiting +on a timer). In that case, we'll do nothing until the user indicates we can proceeed (the timer will fire +regardless of what the user does -- we're just preventing this loop from executing repeatedly when there's nothing +to do). + +.. code:: python + + if action == 'r': + task = self.select_task(current_tasks) + handler = self.handlers.get(type(task.task_spec)) if handler is not None: - handler(next_task) - next_task.complete() + handler(task) + task.run() - workflow.refresh_waiting_tasks() - workflow.do_engine_steps() - if step: - print_state(workflow, next_task, display_types) +In the code above, we present a menu of runnable tasks to the user and run the one they chose, optionally +calling one of our handlers. - print('\nWorkflow Data') - print(json.dumps(workflow.data, indent=2, separators=[ ', ', ': ' ])) +Each task has a `data` attribute, which can by optionally updated when the task is `READY` and before it is +run. The task `data` is just a dictionary. Our handler modifies the task data if necessary (eg adding data +collected from forms), and :code:`task.run` propogates the data to any tasks following it, and changes its state to +one of the `FINISHED` states; nothing more will be done with this task after this point. -The first line of this function is the one that does the bulk of the work in -SpiffWorkflow. Calling :code:`workflow.do_engine_steps()` causes Spiff to repeatedly -look for and execute any engine tasks that are ready. - -An **engine task** does not require user interaction. For instance, it could be -a Script task or selection of a flow from a gateway. Execution will -stop when only interactive tasks remain or the workflow is completed. - -A SpiffWorkflow application will call :code:`workflow.do_engine_steps()` to start the -workflow and then enter a loop that will - -- check for ready user tasks -- present the tasks to the user to complete -- complete the tasks -- refresh any waiting tasks -- complete any engine tasks that have been reached via user interactions - -until the workflow completes. - -When a workflow completes, the task data (just a dictionary passed from one task to the -next, and optionally modified by each task) is copied into the workflow data. We display -the end state of the workflow on completion. - -The rest of the code is all about presenting the tasks to the user and dumping the -workflow state. We've covered former in the BPMN Elements section of :doc:`index` -and will cover the latter in :doc:`advanced`. - -Handling task presentation is what **you** will be developing when you use SpiffWorkflow. - -Examining the Workflow State ----------------------------- - -When this application is run and we want to present steps to the user, we'll need -to be able to examine the workflow and task states and associated data. We'll cover -the basics of this in this section. - -The code below is a simple method for displaying information about a task. We use -this in two ways - -- presenting a list of tasks to a user (in this case the state will always be ready, so we won't include it) -- presenting the state of each task while stepping through the workflow (in this case you most likely do want to know the state). +We'll skip over most of the options in :code:`run_workflow` since they are pretty straightforward. .. code:: python - def format_task(task, include_state=True): - if hasattr(task.task_spec, 'lane') and task.task_spec.lane is not None: - lane = f'[{task.task_spec.lane}]' - else: - lane = '' - state = f'[{task.get_state_name()}]' if include_state else '' - return f'{lane} {task.task_spec.description} ({task.task_spec.name}) {state}' + self.workflow.refresh_waiting_tasks() -We previously went over obtaining the lane information in :doc:`organization`. +At the end of each iteration, we call :code:`refresh_waiting_tasks` to ensure that any currently `WAITING` tasks +will move to `READY` if they are able to do so. -We can call :code:`task.get_state_name()` to get a human-readable representation of -a task's state. - -We store the value provided in the :code:`name` attribute of the task (the text -entered in the 'Name' field in our sample models) in :code:`task.task_spec.description`. - -Here is the code we use for examining the workflow state. +After the workflow finishes, we'll give the user a few options for looking at the end state. .. code:: python - def print_state(workflow, task, display_types): + while action != 'q': + action = self.show_prompt('\nSelect action: ', { + 'a': 'List all tasks', + 'v': 'View workflow data', + 'q': 'Quit', + }) + if action == 'a': + self.list_tasks([t for t in self.workflow.get_tasks() if t.task_spec.bpmn_id is not None], "All Tasks") + elif action == 'v': + dct = self.serializer.data_converter.convert(self.workflow.data) + print('\n' + json.dumps(dct, indent=2, separators=[', ', ': '])) - print('\nLast Task') - print(format_task(task)) - print(json.dumps(task.data, indent=2, separators=[ ', ', ': ' ])) +Note that we're filtering the task lists with :code:`t.task_spec.bpmn_id is not None`. The workflow contains +tasks other than the ones visible on the BPMN diagram; these are tasks that SpiffWorkflow uses to manage execution +and we'll omit them from the displays. If a task is visible on a diagram it will have a non-null value for its +`bpmn_id` attribute (because all BPMN elements require IDs), otherwise the value will be :code:`None`. See +:doc:`advanced` for more information about BPMN task spec attributes. - all_tasks = [ task for task in workflow.get_tasks() if isinstance(task.task_spec, display_types) ] - upcoming_tasks = [ task for task in all_tasks if task.state in [TaskState.READY, TaskState.WAITING] ] +When a workflow completes, the task data from the "End" task, which has built up through the operation of the +workflow, is copied into the workflow data, so we want to give the option to display this end state. We're using +the serializer's `data_converter` to handle the workflow data (the `registry`) we passed in earlier, because +it may contain arbitrary data. - print('\nUpcoming Tasks') - for idx, task in enumerate(upcoming_tasks): - print(format_task(task)) +Let's take a brief look at the advance method: - if input('\nShow all tasks? ').lower() == 'y': - for idx, task in enumerate(all_tasks): - print(format_task(task)) +.. code:: python -We'll print information about our task as described above, as well as a dump of its data. + def advance(self): + engine_tasks = [t for t in self.workflow.get_tasks(TaskState.READY) if not t.task_spec.manual] + while len(engine_tasks) > 0: + for task in engine_tasks: + task.run() + self.workflow.refresh_waiting_tasks() + engine_tasks = [t for t in self.workflow.get_tasks(TaskState.READY) if not t.task_spec.manual] -We can get a list of all tasks regardless of type or state with :code:`workflow.get_tasks()`. +This method is really just a condensed version of :code:`run_workflow` that ignore human tasks and doesn't need to +present a menu. We use it to get to a point in our workflow where there are only human tasks left to run. -The actual list of tasks will get quite long (some tasks are expanded internally by Spiff into -multiple tasks, and all gateways and events are also treated as "tasks"). So we're filtering -the tasks to only display the ones that would have salience to a user here. +In general, an application that uses SpiffWorkflow will use these methods as a template. It will consist of a +loop that: + +* runs any `READY` engine tasks (where :code:`task_spec.manual == False`) +* presents `READY` human tasks to users (if any) +* updates the human task data if necessary +* runs the human tasks +* refreshes any `WAITING` tasks + +until there are no tasks left to complete. + +The rest of the code is all about presenting the tasks to the user and dumping the workflow state. These are the +parts that you'll want to customize in your own application. -We'll further filter those tasks for :code:`READY` and :code:`WAITING` tasks for a more -compact display, and only show all tasks when explicitly called for. diff --git a/SpiffWorkflow/doc/bpmn/tasks.rst b/SpiffWorkflow/doc/bpmn/tasks.rst index 5c25e2d36..41fdaf9d0 100644 --- a/SpiffWorkflow/doc/bpmn/tasks.rst +++ b/SpiffWorkflow/doc/bpmn/tasks.rst @@ -4,46 +4,46 @@ Tasks BPMN Model ---------- -In this example, we'll model a customer selecting a product to illustrate -the basic task types that can be used with SpiffWorkflow. +In this example, we'll model a customer selecting a product to illustrate the basic task types that +can be used with SpiffWorkflow. -We'll be using the following files from `spiff-example-cli `_. +We'll be using the following files from `spiff-example-cli `_: -- `task_types `_ workflow -- `product_prices `_ DMN table +- `task_types `_ workflow +- `product_prices `_ DMN table User Tasks ^^^^^^^^^^ -User tasks would typically be used in the case where the task would be -completed from within the application. - -User tasks can include forms that ask the user questions. When you click on a -user task in a BPMN modeler, the Properties Panel includes a form tab. Use this -tab to build your questions. +User Tasks would typically be used in the case where the task would be completed from within the +application. Our User tasks present forms that collect data from users. We'll ask our hypothetical user to choose a product and quantity. -The following example shows how a form might be set up in Camumda. +.. figure:: figures/tasks/user_task.png + :scale: 30% + :align: center -.. figure:: figures/user_task.png - :scale: 30% - :align: center + User Task - User Task configuration +We can use the form builder to create the form. -.. note:: +.. figure:: figures/tasks/user_task_form.png + :scale: 30% + :align: center - SpiffWorkflow has some basic support for the free Camunda modeler, to use its - form building capabilities, but we intend to encapsulate this support in an - extension module and remove it from the core library eventually. + User Task form See the `Handling User Tasks`_ section for a discussion of sample code. +We have also retained some limited support for the now deprecated +camunda forms, which you can read about in our Camunda Specific section on :doc:`camunda/tasks`. + + Business Rule Tasks ^^^^^^^^^^^^^^^^^^^ -In our business rule task, we'll use a DMN table to look up the price of the +In our Business Rule Task, we'll use a DMN table to look up the price of the product the user chose. We'll need to create a DMN table. @@ -67,7 +67,7 @@ the decision lookup allows the next gateway or activity to route the flow. Our Business Rule Task will make use of a DMN table. -.. figure:: figures/dmn_table.png +.. figure:: figures/tasks/dmn_table.png :scale: 30% :align: center @@ -81,7 +81,7 @@ Our Business Rule Task will make use of a DMN table. Then we'll refer to this table in the task configuration. -.. figure:: figures/business_rule_task.png +.. figure:: figures/tasks/business_rule_task.png :scale: 30% :align: center @@ -91,9 +91,9 @@ Script Tasks ^^^^^^^^^^^^ The total order cost will need to be calculated on the fly. We can do this in -a script task. We'll configure the task with some simple Python code. +a Script Task. We'll configure the task with some simple Python code. -.. figure:: figures/script_task.png +.. figure:: figures/tasks/script_task.png :scale: 30% :align: center @@ -105,36 +105,45 @@ have been defined previously will be available to it. Manual Tasks ^^^^^^^^^^^^ -Our final task type is a manual task. We would use this task in the situation -where the application might simply need to mark a task that requires user -involvement complete without gathering any additional information from them. +Our final task type is a Manual Task. Manual Tasks represent work that occures +outside of SpiffWorkflow's control. Say that you need to include a step in a +process where the participant needs to stand up, walk over to the coffee maker, +and poor the cup of coffee. Manual Tasks pause the process, and wait for +confirmation that the step was completed. -There is no special configuration for manual tasks. However, this is a good -place to note that we can use the BPMN element Documentation field to display -more information about the context of the item. +Text that will be displayed to the user is added in the "Instructions" panel. -Spiff is set up in a way that you could use any templating library you want, but -we have used `Jinja `_. - -In this example, we'll present an order summary to our customer. - -.. figure:: figures/documentation.png +.. figure:: figures/tasks/manual_task.png :scale: 30% :align: center - Element Documentation + Manual Task + +Spiff's manual tasks may contain references to data inside the workflow. We have used +`Jinja `_, but Spiff is set up in a way that +you could use any templating library you want, as well as Markdown formatting directives +(we won't implement those here though, because it doesn't make sense for a command +line app). + +.. figure:: figures/tasks/manual_task_instructions.png + :scale: 30% + :align: center + + Editing Instructions See the `Handling Manual Tasks`_ section for a discussion of sample code. +For information about how Spiff handles Manual Tasks created with Camunda please +refer to the Camunda Specific section on :doc:`camunda/tasks`. + Running The Model ^^^^^^^^^^^^^^^^^ -If you have set up our example repository, this model can be run with the -following command: +If you have set up our example repository, this model can be run with the following command: .. code-block:: console - ./run.py -p order_product -d bpmn/product_prices.dmn -b bpmn/task_types.bpmn + ./spiff-bpmn-runner.py -p order_product -d bpmn/tutorial/product_prices.dmn -b bpmn/tutorial/task_types.bpmn Example Application Code ------------------------ @@ -147,50 +156,49 @@ responses. .. code:: python - for field in task.task_spec.form.fields: - if isinstance(field, EnumFormField): - option_map = dict([ (opt.name, opt.id) for opt in field.options ]) - options = "(" + ', '.join(option_map) + ")" - prompt = f"{field.label} {options} " - option = select_option(prompt, option_map.keys()) - response = option_map[option] - else: - response = input(f"{field.label} ") - if field.type == "long": - response = int(response) - task.update_data_var(field.id, response) + filename = task.task_spec.extensions['properties']['formJsonSchemaFilename'] + schema = json.load(open(os.path.join(forms_dir, filename))) + for field, config in schema['properties'].items(): + if 'oneOf' in config: + option_map = dict([ (v['title'], v['const']) for v in config['oneOf'] ]) + options = "(" + ', '.join(option_map) + ")" + prompt = f"{field} {options} " + option = input(prompt) + while option not in option_map: + print(f'Invalid selection!') + option = input(prompt) + response = option_map[option] + else: + response = input(f"{config['title']} ") + if config['type'] == 'integer': + response = int(response) + task.data[field] = response -The list of form fields for a task is stored in :code:`task.task_spec.form_fields`. +SpiffWorkflow uses JSON Schema to represent forms, specifically +`react-jsonschema-form `_. +Our forms are really intended to be displayed in a browser, and attempting to handle them in a command +line appliction is a little awkward. The form specifications can be quite complex. -For Enumerated fields, we want to get the possible options and present them to the -user. The variable names of the fields were stored in :code:`field.id`, but since -we set labels for each of the fields, we'd like to display those instead, and map -the user's selection back to the variable name. +This simple implementation will present a list of options for simple enumerated fields and simply +directly stores whatever the user enters otherwise, with integer conversions if the field is so +specified. This is robust enough to collect enough information from a user to make it through our example. -Our :code:`select_option` function simply repeats the prompt until the user -enters a value contained in the option list. - -For other fields, we'll just store whatever the user enters, although in the case -where the data type was specified to be a :code:`long`, we'll convert it to a -number. - -Finally, we need to explicitly store the user-provided response in a variable -with the expected name with :code:`task.update_data_var(field.id, response)`. +SpiffWorkflow provides a mechanism for you to provide your own form specification and leaves it up to you +to decide how to present it. Handling Business Rule Tasks ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -We do not need to do any special configuration to handle these business rule -tasks. SpiffWorkflow does it all for us. +We do not need to do any special configuration to handle these Business Rule Tasks. SpiffWorkflow does it all for us. Handling Script Tasks ^^^^^^^^^^^^^^^^^^^^^ -We do not need to do any special configuration to handle script tasks, although it +We do not need to do any special configuration to handle Script Tasks, although it is possible to implement a custom script engine. We demonstrate that process in Custom Script Engines section :doc:`advanced` features. However, the default script -engine will work in many cases. +engine will be adequate for now. Handling Manual Tasks ^^^^^^^^^^^^^^^^^^^^^ @@ -201,21 +209,26 @@ completed. .. code:: python def complete_manual_task(task): - display_task(task) + display_instructions(task) input("Press any key to mark task complete") -:code:`display_task()` is the code for converting the Documentation property of the task -into something that can be presented to the user. +:code:`display_instructions` handles presenting the task to the user. .. code:: python - def display_task(task): - print(f'\n{task.task_spec.description}') - if task.task_spec.documentation is not None: - template = Template(task.task_spec.documentation) + def display_instructions(task): + text = task.task_spec.extensions.get('instructionsForEndUser') + print(f'\n{task.task_spec.bpmn_name}') + if text is not None: + template = Template(text) print(template.render(task.data)) -The template string can be obtained from :code:`task.task_spec.documentation`. +The template string can be obtained from :code:`task.task_spec.extensions.get('instructionsForEndUser')`. As noted above, our template class comes from Jinja. We render the template using the task data, which is just a dictionary. + +.. note:: + + Most of Spiff's task specifications contain this extension, not just Manual Tasks. We also use it to display + information along with forms, and about certain events. diff --git a/SpiffWorkflow/doc/concepts.rst b/SpiffWorkflow/doc/concepts.rst new file mode 100644 index 000000000..28554f115 --- /dev/null +++ b/SpiffWorkflow/doc/concepts.rst @@ -0,0 +1,120 @@ +SpiffWorkflow Concepts +====================== + +Specifications vs. Instances +---------------------------- + +SpiffWorkflow consists of two different categories of objects: + +- **Specification objects**, which represent the definitions and derive from :code:`WorkflowSpec` and :code:`TaskSpec` +- **Instance objects**, which represent the state of a running workflow (:code:`Workflow`, :code:`BpmnWorkflow` and :code:`Task`) + +In the workflow context, a specification is model of the workflow, an abstraction that describes *every path that could +be taken whenever the workflow is executed*. An instance is a particular instantiation of a specification. It describes *the +current state* or *the path(s) that were actually taken when the workflow ran*. + +In the task context, a specification is a model for how a task behaves. It describes the mechanisms for deciding *whether +there are preconditions for running an associated task*, *how to decide whether they are met*, and *what it means to complete +(successfully or unsuccessfully)*. An instance describes the *state of the task, as it pertains to a particular workflow* and +*contains the data used to manage that state*. + +Specifications are unique, whereas instances are not. There is *one* model of a workflow, and *one* specification for a particular task. + +Imagine a workflow with a loop. The loop is defined once in the specification, but there can be many tasks associated with +each of the specs that comprise the loop. + +In our BPMN example, described a product selection process.:: + + Start -> Select and Customize Product -> Continue Shopping? + +Since the customer can potentially select more than one product, how our instance looks depends on the customer's actions. If +they choose three products, then we get the following tree:: + + Start --> Select and Customize Product -> Continue Shopping? + |-> Select and Customize Product -> Continue Shopping? + |-> Select and Customize Product -> Continue Shopping? + +There is *one* TaskSpec describing product selection and customization and *one* TaskSpec that determines whether to add more +items, but it may execute any number of imes, resulting in as many Tasks for these TaskSpecs as the number of products the +customer selects. + +Understanding Task States +------------------------- + +* **PREDICTED** Tasks + + A predicted task is one that will possibly, but not necessarily run at a future time. For example, if a task follows a + conditional gateway, which path is taken won't be known until the gateway is reached and the conditions evaluated. There + are two types of predicted tasks: + + - **MAYBE**: The task is part of a conditional path + - **LIKELY** : The task is the default output on a conditional path + +* **DEFINITE** Tasks + + Definite tasks are certain to run as the workflow pregresses. + + - **FUTURE**: The task will definitely run. + - **WAITING**: A condition must be met before the task can become **READY** + - **READY**: The preconditions for running this task have been met + - **STARTED**: The task has started running but has not finished + +* **FINISHED** Tasks + + A finished task is one where no further action will be taken. + + - **COMPLETED**: The task finished successfully. + - **ERROR**: The task finished unsucessfully. + - **CANCELLED**: The task was cancelled before it ran or while it was running. + + + +Tasks start in either a **PREDICTED** or **FUTURE** state, move through one or more **DEFINITE** states, and end in a +**FINISHED** state. State changes are determined by several task spec methods: + +Hooks +======= + +SpiffWorkflow executes a Task by calling a series of hooks that are tightly coupled +to Task State. These hooks are: + +* `_update_hook`: This method will be run by a task's predecessor when the predecessor completes. The method checks the + preconditions for running the task and returns a boolean indicating whether a task should become **READY**. Otherwise, + the state will be set to **WAITING**. + +* `_on_ready_hook`: This method will be run when the task becomes **READY** (but before it runs). + +* `run_hook`: This method implements the task's behavior when it is run, returning: + + - :code:`True` if the task completed successfully. The state will transition to **COMPLETED**. + - :code:`False` if the task completed unsucessfully. The state will transition to **ERRROR**. + - :code:`None` if the task has not completed. The state will transition to **STARTED**. + +* `_on_complete_hook`: This method will be run when the task's state is changed to **COMPLETED**. + +* `_on_error_hook`: This method will be run when the task's state is changed to **ERROR**. + +* `_on_trigger`: This method executes the task's behavior when it is triggered (`Trigger` tasks only). + +Task Prediction +--------------- + +Each TaskSpec also has a `_predict_hook` method, which is used to set the state of not-yet-executed children. The behavior +of `_predict_hook` varies by TaskSpec. This is the mechanism that determines whether Tasks are **FUTURE**, **LIKELY**, or +**MAYBE**. When a workflow is created, a task tree is generated that contains all definite paths, and branches of +**PREDICTED** tasks with a maximum length of two. If a **PREDICTED** task becomes **DEFINITE**, the Task's descendants +are re-predicted. If it's determined that a **PREDICTED** will not run, the task and all its descendants will be dropped +from the tree. By default `_on_predict_hook` will ignore **DEFINITE** tasks, but this can be overridden by providing a +mask of `TaskState` values that specifies states other than **PREDICTED**. + +Where Data is Stored +-------------------- + +Data can ba associated with worklows in the following ways: + +- **Workflow data** is stored on the Workflow, with changes affecting all Tasks. +- **Task data** is local to the Task, initialized from the data of the Task's parent. +- **Task internal data** is local to the Task and not passed to the Task's children +- **Task spec data** is stored in the TaskSpec object, and if updated, the updates will apply to any Task that references the spec + (unused by the :code:`bpmn` package and derivatives). + diff --git a/SpiffWorkflow/doc/conf.py b/SpiffWorkflow/doc/conf.py index 8cbd761e7..3df6157ec 100644 --- a/SpiffWorkflow/doc/conf.py +++ b/SpiffWorkflow/doc/conf.py @@ -18,11 +18,11 @@ # -- Project information ----------------------------------------------------- project = 'SpiffWorkflow' -copyright = '2022, Sartography' +copyright = '2023, Sartography' author = 'Sartography' # The full version, including alpha/beta/rc tags -release = '1.2.1' +release = '2.0.0rc0' # -- General configuration --------------------------------------------------- diff --git a/SpiffWorkflow/doc/non-bpmn/custom-tasks/index.rst b/SpiffWorkflow/doc/core/custom-tasks/index.rst similarity index 91% rename from SpiffWorkflow/doc/non-bpmn/custom-tasks/index.rst rename to SpiffWorkflow/doc/core/custom-tasks/index.rst index 916a148bf..81fd25fb0 100644 --- a/SpiffWorkflow/doc/non-bpmn/custom-tasks/index.rst +++ b/SpiffWorkflow/doc/core/custom-tasks/index.rst @@ -7,9 +7,8 @@ Introduction In this second tutorial, we are going to implement our own task, and use serialization and deserialization to store and restore it. -If you haven't already, you should complete the first -:doc:`../tutorial/index`. -We are also assuming that you are familiar with the :doc:`../basics`. +If you haven't already, you should complete the first :doc:`../tutorial/index`. +We are also assuming that you are familiar with the :doc:`../../concepts`. Implementing the custom task ---------------------------- diff --git a/SpiffWorkflow/doc/non-bpmn/custom-tasks/nuclear.json b/SpiffWorkflow/doc/core/custom-tasks/nuclear.json similarity index 100% rename from SpiffWorkflow/doc/non-bpmn/custom-tasks/nuclear.json rename to SpiffWorkflow/doc/core/custom-tasks/nuclear.json diff --git a/SpiffWorkflow/doc/non-bpmn/custom-tasks/serializer.py b/SpiffWorkflow/doc/core/custom-tasks/serializer.py similarity index 100% rename from SpiffWorkflow/doc/non-bpmn/custom-tasks/serializer.py rename to SpiffWorkflow/doc/core/custom-tasks/serializer.py diff --git a/SpiffWorkflow/doc/non-bpmn/custom-tasks/start.py b/SpiffWorkflow/doc/core/custom-tasks/start.py similarity index 59% rename from SpiffWorkflow/doc/non-bpmn/custom-tasks/start.py rename to SpiffWorkflow/doc/core/custom-tasks/start.py index 472d20997..fc6486b91 100644 --- a/SpiffWorkflow/doc/non-bpmn/custom-tasks/start.py +++ b/SpiffWorkflow/doc/core/custom-tasks/start.py @@ -1,13 +1,12 @@ -import json -from SpiffWorkflow import Workflow -from SpiffWorkflow.specs import WorkflowSpec +from SpiffWorkflow.workflow import Workflow +from SpiffWorkflow.specs.WorkflowSpec import WorkflowSpec from serializer import NuclearSerializer # Load from JSON with open('nuclear.json') as fp: workflow_json = fp.read() -serializer = NuclearSerializer() -spec = WorkflowSpec.deserialize(serializer, workflow_json) +nuclear_serializer = NuclearSerializer() +spec = WorkflowSpec.deserialize(nuclear_serializer, workflow_json) # Create the workflow. workflow = Workflow(spec) @@ -15,4 +14,4 @@ workflow = Workflow(spec) # Execute until all tasks are done or require manual intervention. # For the sake of this tutorial, we ignore the "manual" flag on the # tasks. In practice, you probably don't want to do that. -workflow.complete_all(halt_on_manual=False) +workflow.run_all(halt_on_manual=False) diff --git a/SpiffWorkflow/doc/non-bpmn/custom-tasks/strike.py b/SpiffWorkflow/doc/core/custom-tasks/strike.py similarity index 88% rename from SpiffWorkflow/doc/non-bpmn/custom-tasks/strike.py rename to SpiffWorkflow/doc/core/custom-tasks/strike.py index 8dbaf2a24..3f489a9df 100644 --- a/SpiffWorkflow/doc/non-bpmn/custom-tasks/strike.py +++ b/SpiffWorkflow/doc/core/custom-tasks/strike.py @@ -1,4 +1,4 @@ -from SpiffWorkflow.specs import Simple +from SpiffWorkflow.specs.Simple import Simple class NuclearStrike(Simple): def _on_complete_hook(self, my_task): diff --git a/SpiffWorkflow/doc/core/index.rst b/SpiffWorkflow/doc/core/index.rst new file mode 100644 index 000000000..a20d6ecc4 --- /dev/null +++ b/SpiffWorkflow/doc/core/index.rst @@ -0,0 +1,14 @@ +Core Library +============ + +SpiffWorkflow's BPMN support is built on top of a core library that aims to be a general workflow +execution environment. Workflow specifications can be created from a simple XML format, or even +easily in python code. It supports a wide range of task specifications and workflow patterns, making +it amenable to adaptation to many different schemas for defining workflow behavior. + +.. toctree:: + :maxdepth: 2 + + tutorial/index + custom-tasks/index + patterns diff --git a/SpiffWorkflow/doc/non-bpmn/patterns.rst b/SpiffWorkflow/doc/core/patterns.rst similarity index 97% rename from SpiffWorkflow/doc/non-bpmn/patterns.rst rename to SpiffWorkflow/doc/core/patterns.rst index 0b659631a..23dc901ae 100644 --- a/SpiffWorkflow/doc/non-bpmn/patterns.rst +++ b/SpiffWorkflow/doc/core/patterns.rst @@ -5,7 +5,7 @@ Supported Workflow Patterns .. HINT:: All examples are located - `here `_. + `here `_ Control-Flow Patterns --------------------- diff --git a/SpiffWorkflow/doc/non-bpmn/tutorial/deserialize-wf.py b/SpiffWorkflow/doc/core/tutorial/deserialize-wf.py similarity index 100% rename from SpiffWorkflow/doc/non-bpmn/tutorial/deserialize-wf.py rename to SpiffWorkflow/doc/core/tutorial/deserialize-wf.py diff --git a/SpiffWorkflow/doc/non-bpmn/tutorial/deserialize.py b/SpiffWorkflow/doc/core/tutorial/deserialize.py similarity index 100% rename from SpiffWorkflow/doc/non-bpmn/tutorial/deserialize.py rename to SpiffWorkflow/doc/core/tutorial/deserialize.py diff --git a/SpiffWorkflow/doc/non-bpmn/tutorial/index.rst b/SpiffWorkflow/doc/core/tutorial/index.rst similarity index 97% rename from SpiffWorkflow/doc/non-bpmn/tutorial/index.rst rename to SpiffWorkflow/doc/core/tutorial/index.rst index 2ba301184..f1f2c02cf 100644 --- a/SpiffWorkflow/doc/non-bpmn/tutorial/index.rst +++ b/SpiffWorkflow/doc/core/tutorial/index.rst @@ -7,7 +7,7 @@ Introduction In this chapter we are going to use Spiff Workflow to solve a real-world problem: We will create a workflow for triggering a nuclear strike. -We are assuming that you are familiar with the :doc:`../basics`. +We are assuming that you are familiar with the :doc:`../../concepts`. Assume you want to send the rockets, but only after both the president and a general have signed off on it. diff --git a/SpiffWorkflow/doc/non-bpmn/tutorial/nuclear.json b/SpiffWorkflow/doc/core/tutorial/nuclear.json similarity index 86% rename from SpiffWorkflow/doc/non-bpmn/tutorial/nuclear.json rename to SpiffWorkflow/doc/core/tutorial/nuclear.json index 7f0efb2a8..e663c6d0a 100644 --- a/SpiffWorkflow/doc/non-bpmn/tutorial/nuclear.json +++ b/SpiffWorkflow/doc/core/tutorial/nuclear.json @@ -2,23 +2,23 @@ "task_specs": { "Start": { "class": "SpiffWorkflow.specs.StartTask.StartTask", - "id" : 1, + "id" : 1, "manual": false, "outputs": [ - 2 + "general" ] }, "general": { "class": "SpiffWorkflow.specs.ExclusiveChoice.ExclusiveChoice", "name": "general", - "id" : 2, + "id" : 2, "manual": true, "inputs": [ - 1 + "Start" ], "outputs": [ - 5, - 3 + "workflow_aborted", + "president" ], "choice": null, "default_task_spec": "workflow_aborted", @@ -44,14 +44,14 @@ "president": { "class": "SpiffWorkflow.specs.ExclusiveChoice.ExclusiveChoice", "name": "president", - "id" : 3, + "id" : 3, "manual": true, "inputs": [ - 2 + "general" ], "outputs": [ - 5, - 4 + "workflow_aborted", + "nuclear_strike" ], "choice": null, "default_task_spec": "workflow_aborted", @@ -75,11 +75,11 @@ ] }, "nuclear_strike": { - "id" : 4, + "id" : 4, "class": "SpiffWorkflow.specs.Simple.Simple", "name": "nuclear_strike", "inputs": [ - 3 + "president" ] }, "workflow_aborted": { @@ -87,8 +87,8 @@ "class": "SpiffWorkflow.specs.Cancel.Cancel", "name": "workflow_aborted", "inputs": [ - 2, - 3 + "general", + "president" ] } }, diff --git a/SpiffWorkflow/doc/non-bpmn/tutorial/nuclear.py b/SpiffWorkflow/doc/core/tutorial/nuclear.py similarity index 100% rename from SpiffWorkflow/doc/non-bpmn/tutorial/nuclear.py rename to SpiffWorkflow/doc/core/tutorial/nuclear.py diff --git a/SpiffWorkflow/doc/non-bpmn/tutorial/serialize-wf.py b/SpiffWorkflow/doc/core/tutorial/serialize-wf.py similarity index 100% rename from SpiffWorkflow/doc/non-bpmn/tutorial/serialize-wf.py rename to SpiffWorkflow/doc/core/tutorial/serialize-wf.py diff --git a/SpiffWorkflow/doc/non-bpmn/tutorial/serialize.py b/SpiffWorkflow/doc/core/tutorial/serialize.py similarity index 100% rename from SpiffWorkflow/doc/non-bpmn/tutorial/serialize.py rename to SpiffWorkflow/doc/core/tutorial/serialize.py diff --git a/SpiffWorkflow/doc/non-bpmn/tutorial/start.py b/SpiffWorkflow/doc/core/tutorial/start.py similarity index 98% rename from SpiffWorkflow/doc/non-bpmn/tutorial/start.py rename to SpiffWorkflow/doc/core/tutorial/start.py index 93d6aec55..9a287f15e 100644 --- a/SpiffWorkflow/doc/non-bpmn/tutorial/start.py +++ b/SpiffWorkflow/doc/core/tutorial/start.py @@ -1,4 +1,3 @@ -import json from SpiffWorkflow.workflow import Workflow from SpiffWorkflow.specs.WorkflowSpec import WorkflowSpec from SpiffWorkflow.serializer.json import JSONSerializer diff --git a/SpiffWorkflow/doc/development.rst b/SpiffWorkflow/doc/development.rst deleted file mode 100644 index 175446e93..000000000 --- a/SpiffWorkflow/doc/development.rst +++ /dev/null @@ -1,99 +0,0 @@ -SpiffWorkflow Concepts -==================================== - -Specification vs. Workflow Instance ------------------------------------ - -One critical concept to know about SpiffWorkflow is the difference between a -:class:`SpiffWorkflow.specs.WorkflowSpec` and :class:`SpiffWorkflow.Workflow` and -the difference between a :class:`SpiffWorkflow.specs.TaskSpec` and :class:`SpiffWorkflow.Task`. - -In order to understand how to handle a running workflow consider the following process:: - - Choose product -> Choose amount -> Produce product A - `--> Produce product B - -As you can see, in this case the process resembles a simple tree. *Choose product*, -*Choose amount*, *Produce product A*, and *Produce product B* are all specific kinds -of *task specifications*, and the whole process is a *workflow specification*. - -But when you execute the workflow, the path taken does not necessarily have the same shape. For example, if the user chooses to produce 3 items of product A, the path taken looks like the following:: - - Choose product -> Choose amount -> Produce product A - |--> Produce product A - `--> Produce product A - -This is the reason why you will find two different categories of objects in Spiff Workflow: - -- **Specification objects** (WorkflowSpec and TaskSpec) represent the workflow definition, and -- **derivation tree objects** (Workflow and Task) model the task tree that represents the state of a running workflow. - -Understanding task states -------------------------- - -The following task states exist: - -.. image:: figures/state-diagram.png - -The states are reached in a strict order and the lines in the diagram show the possible state transitions. - -The order of these state transitions is violated only in one case: A *Trigger* task may add additional work to a task that was already COMPLETED, causing it to change the state back to FUTURE. - -- **MAYBE** means that the task will possibly, but not necessarily run at a future time. This means that it can not yet be fully determined as to whether or not it may run, for example, because the execution still depends on the outcome of an ExclusiveChoice task in the path that leads towards it. - -- **LIKELY** is like MAYBE, except it is considered to have a higher probability of being reached because the path leading towards it is the default choice in an ExclusiveChoice task. - -- **FUTURE** means that the processor has predicted that this this path will be taken and this task will, at some point, definitely run. (Unless the task is explicitly set to CANCELLED, which can not be predicted.) If a task is waiting on predecessors to run then it is in FUTURE state (not WAITING). - -- **WAITING** means *I am in the process of doing my work and have not finished. When the work is finished, then I will be READY for completion and will go to READY state*. WAITING is an optional state. - -- **READY** means "the preconditions for marking this task as complete are met". - -- **COMPLETED** means that the task is done. - -- **CANCELLED** means that the task was explicitly cancelled, for example by a CancelTask operation. - -Associating data with a workflow --------------------------------- - -The difference between *specification objects* and *derivation tree objects* is also important when choosing how to store data in a workflow. Spiff Workflow supports storing data in two ways: - -- **Task spec data** is stored in the TaskSpec object. In other words, if a task causes task spec data to change, that change is reflected to all other instances in the derivation tree that use the TaskSpec object. -- **Task data** is local to the Task object, but is carried along to the children of each Task object in the derivation tree as the workflow progresses. - -Internal Details ----------------- - -A **derivation tree** is created based off of the spec using a hierarchy of -:class:`SpiffWorkflow.Task` objects (not :class:`SpiffWorkflow.specs.TaskSpec` objects!). -Each Task contains a reference to the TaskSpec that generated it. - -Think of a derivation tree as tree of execution paths (some, but not all, of -which will end up executing). Each Task object is basically a node in the -derivation tree. Each task in the tree links back to its parent (there are -no connection objects). The processing is done by walking down the -derivation tree one Task at a time and moving the task (and its -children) through the sequence of states towards completion. - -You can serialize/deserialize specs. You can also -serialize/deserialize a running workflow (it will pull in its spec as well). - -There's a decent eventing model that allows you to tie in to and receive -events (for each task, you can get event notifications from its TaskSpec). -The events correspond with how the processing is going in the derivation -tree, not necessarily how the workflow as a whole is moving. -See :class:`SpiffWorkflow.specs.TaskSpec` for docs on events. - -You can nest workflows (using the :class:`SpiffWorkflow.specs.SubWorkflowSpec`). - -The serialization code is done well which makes it easy to add new formats -if we need to support them. - - -Other documentation -------------------- - -**API documentation** is currently embedded into the Spiff Workflow source code and not yet made available in a prettier form. - -If you need more help, please create an issue in our -`issue tracker `_. diff --git a/SpiffWorkflow/doc/index.rst b/SpiffWorkflow/doc/index.rst index 12c065325..13bbf2986 100644 --- a/SpiffWorkflow/doc/index.rst +++ b/SpiffWorkflow/doc/index.rst @@ -1,12 +1,8 @@ .. image:: https://travis-ci.com/sartography/SpiffWorkflow.svg?branch=master :target: https://travis-ci.org/sartography/SpiffWorkflow -.. image:: https://sonarcloud.io/api/project_badges/measure?project=sartography_SpiffWorkflow&metric=alert_status - :target: https://sonarcloud.io/dashboard?id=sartography_SpiffWorkflow - -.. image:: https://sonarcloud.io/api/project_badges/measure?project=sartography_SpiffWorkflow&metric=coverage - :target: https://sonarcloud.io/dashboard?id=sartography_SpiffWorkflow - :alt: Coverage +.. image:: https://github.com/sartography/SpiffWorkflow/actions/workflows/tests.yaml/badge.svg + :target: https://github.com/sartography/SpiffWorkflow/actions/workflows/tests.yaml .. image:: https://img.shields.io/github/stars/sartography/SpiffWorkflow.svg :target: https://github.com/sartography/SpiffWorkflow/stargazers @@ -14,20 +10,25 @@ .. image:: https://img.shields.io/github/license/sartography/SpiffWorkflow.svg :target: https://github.com/sartography/SpiffWorkflow/blob/master/COPYING + What is SpiffWorkflow? ====================== + .. image:: images/logo.png :align: center :target: https://www.spiffworkflow.org -SpiffWorkflow allows your python application to process BPMN diagrams (think -of them as very powerful flow charts, See :doc:`intro`.) to accomplish -what would otherwise require writing a lot of complex business logic in your +**SpiffWorkflow is a library that provides a flexible workflow execution environment.** + +Recent development has largely focused on allowing Python applications to process +BPMN diagrams (think of them as very powerful flow charts; see :doc:`bpmn/intro`). to +accomplish what would otherwise require writing a lot of complex business logic in your code. You can use these diagrams to accomplish a number of tasks, such as: - Creating a questionnaire with multiple complex paths - Implement an approval process that requires input from multiple users - - Allow non-programmers to modify the flow and behavior of your application. + - Allow non-programmers to modify the flow and behavior of your application + - Visualize and manage long running data processing tasks Please visit `SpiffWorkflow.org `_ for additional articles, videos, and tutorials about SpiffWorkflow and its @@ -53,8 +54,8 @@ Contents .. toctree:: :maxdepth: 2 - intro - bpmn/index - development - non-bpmn/index + bpmn/overview + bpmn/intro + concepts + core/index diff --git a/SpiffWorkflow/doc/non-bpmn/index.rst b/SpiffWorkflow/doc/non-bpmn/index.rst deleted file mode 100644 index 3f3f36bc9..000000000 --- a/SpiffWorkflow/doc/non-bpmn/index.rst +++ /dev/null @@ -1,12 +0,0 @@ -Non-BPMN support -================ - -We have maintained support for legacy non-BPMN workflows, but we recommend using -SpiffWorkflow with BPMN, as this is where current development is focused. - -.. toctree:: - :maxdepth: 2 - - tutorial/index - custom-tasks/index - patterns diff --git a/SpiffWorkflow/pyproject.toml b/SpiffWorkflow/pyproject.toml index 864b334a8..337bbf038 100644 --- a/SpiffWorkflow/pyproject.toml +++ b/SpiffWorkflow/pyproject.toml @@ -1,3 +1,6 @@ [build-system] requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta:__legacy__" + +[tool.ruff] +line-length = 140 diff --git a/SpiffWorkflow/requirements.txt b/SpiffWorkflow/requirements.txt index bf928ff6e..aa9c15802 100644 --- a/SpiffWorkflow/requirements.txt +++ b/SpiffWorkflow/requirements.txt @@ -1,6 +1,4 @@ -# Celery locked to 5.2.3 due to https://github.com/celery/celery/issues/7409 # Can remove this limitation when bug is resolved, or we drop support for python 3.7 -celery==5.2.3 coverage lxml . diff --git a/SpiffWorkflow/scripts/test_times.py b/SpiffWorkflow/scripts/test_times.py index 737983fa5..df4da98fb 100755 --- a/SpiffWorkflow/scripts/test_times.py +++ b/SpiffWorkflow/scripts/test_times.py @@ -1,5 +1,23 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- + +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA import re import sys diff --git a/SpiffWorkflow/setup.py b/SpiffWorkflow/setup.py index 3429710d2..20b6c9735 100644 --- a/SpiffWorkflow/setup.py +++ b/SpiffWorkflow/setup.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- import pathlib import sys +from setuptools import setup, find_packages sys.path.insert(0, '.') sys.path.insert(0, 'SpiffWorkflow') -from setuptools import setup, find_packages # The directory containing this file HERE = pathlib.Path(__file__).parent @@ -13,7 +13,7 @@ HERE = pathlib.Path(__file__).parent README = (HERE / "README.md").read_text() setup(name='SpiffWorkflow', - version='1.2.1', + version='2.0.0rc0', description='A workflow framework and BPMN/DMN Processor', long_description=README, long_description_content_type="text/markdown", @@ -25,7 +25,7 @@ setup(name='SpiffWorkflow', 'SpiffWorkflow.bpmn.parser': ['schema/*.xsd'], 'SpiffWorkflow.dmn.parser': ['schema/*.xsd'], }, - install_requires=['configparser', 'lxml', 'celery', + install_requires=['configparser', 'lxml', # required for python 3.7 - https://stackoverflow.com/a/73932581 'importlib-metadata<5.0; python_version <= "3.7"'], keywords='spiff workflow bpmn engine', diff --git a/SpiffWorkflow/sonar-project.properties b/SpiffWorkflow/sonar-project.properties deleted file mode 120000 index 56b26ee9b..000000000 --- a/SpiffWorkflow/sonar-project.properties +++ /dev/null @@ -1 +0,0 @@ -.sonarcloud.properties \ No newline at end of file diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/BpmnLoaderForTests.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/BpmnLoaderForTests.py index 376975ba9..2432b0b92 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/BpmnLoaderForTests.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/BpmnLoaderForTests.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from SpiffWorkflow.bpmn.specs.data_spec import BpmnDataStoreSpecification -from SpiffWorkflow.bpmn.specs.ExclusiveGateway import ExclusiveGateway -from SpiffWorkflow.bpmn.specs.UserTask import UserTask +from SpiffWorkflow.bpmn.specs.defaults import ExclusiveGateway +from SpiffWorkflow.bpmn.specs.defaults import UserTask from SpiffWorkflow.bpmn.parser.BpmnParser import BpmnParser from SpiffWorkflow.bpmn.parser.TaskParser import TaskParser from SpiffWorkflow.bpmn.parser.task_parsers import ConditionalGatewayParser @@ -10,15 +10,9 @@ from SpiffWorkflow.bpmn.parser.util import full_tag from SpiffWorkflow.bpmn.serializer.helpers.spec import BpmnSpecConverter, TaskSpecConverter -# Many of our tests relied on the Packager to set the calledElement attribute on -# Call Activities. I've moved that code to a customized parser. -from SpiffWorkflow.signavio.parser.tasks import CallActivityParser -from SpiffWorkflow.bpmn.specs.SubWorkflowTask import CallActivity - __author__ = 'matth' -# This provides some extensions to the BPMN parser that make it easier to -# implement testcases +# One glorious day I will be able to remove these classes. class TestUserTask(UserTask): @@ -52,7 +46,6 @@ class TestUserTaskConverter(TaskSpecConverter): def to_dict(self, spec): dct = self.get_default_attributes(spec) - dct.update(self.get_bpmn_attributes(spec)) return dct def from_dict(self, dct): @@ -64,12 +57,12 @@ class TestDataStore(BpmnDataStoreSpecification): def get(self, my_task): """Copy a value from a data store into task data.""" - my_task.data[self.name] = TestDataStore._value + my_task.data[self.bpmn_id] = TestDataStore._value def set(self, my_task): """Copy a value from the task data to the data store""" - TestDataStore._value = my_task.data[self.name] - del my_task.data[self.name] + TestDataStore._value = my_task.data[self.bpmn_id] + del my_task.data[self.bpmn_id] class TestDataStoreConverter(BpmnSpecConverter): @@ -78,8 +71,8 @@ class TestDataStoreConverter(BpmnSpecConverter): def to_dict(self, spec): return { - "name": spec.name, - "description": spec.description, + "bpmn_id": spec.bpmn_id, + "bpmn_name": spec.bpmn_name, "capacity": spec.capacity, "is_unlimited": spec.is_unlimited, "_value": TestDataStore._value, @@ -95,7 +88,6 @@ class TestBpmnParser(BpmnParser): OVERRIDE_PARSER_CLASSES = { full_tag('userTask'): (TaskParser, TestUserTask), full_tag('exclusiveGateway'): (TestExclusiveGatewayParser, ExclusiveGateway), - full_tag('callActivity'): (CallActivityParser, CallActivity) } DATA_STORE_CLASSES = { diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py index 1896acde8..604ba1f68 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py @@ -67,13 +67,13 @@ class BpmnWorkflowTestCase(unittest.TestCase): return p.workflow.get_task_from_id(task_id) def is_match(t): - if not (t.task_spec.name == step_name_path[-1] or t.task_spec.description == step_name_path[-1]): + if not (t.task_spec.name == step_name_path[-1] or t.task_spec.bpmn_name == step_name_path[-1]): return False for parent_name in step_name_path[:-1]: p = t.parent found = False while (p and p != p.parent): - if (p.task_spec.name == parent_name or p.task_spec.description == parent_name): + if (p.task_spec.name == parent_name or p.task_spec.bpmn_name == parent_name): found = True break if p.parent is None and p.workflow != p.workflow.outer_workflow: @@ -84,15 +84,14 @@ class BpmnWorkflowTestCase(unittest.TestCase): return False return True - tasks = list( - [t for t in self.workflow.get_tasks(TaskState.READY) if is_match(t)]) + tasks = [t for t in self.workflow.get_tasks(TaskState.READY) if is_match(t)] self._do_single_step( step_name_path[-1], tasks, set_attribs, choice, only_one_instance=only_one_instance) def assertTaskNotReady(self, step_name): tasks = list([t for t in self.workflow.get_tasks(TaskState.READY) - if t.task_spec.name == step_name or t.task_spec.description == step_name]) + if t.task_spec.name == step_name or t.task_spec.bpmn_name == step_name]) self.assertEqual([], tasks) def _do_single_step(self, step_name, tasks, set_attribs=None, choice=None, only_one_instance=True): @@ -106,8 +105,8 @@ class BpmnWorkflowTestCase(unittest.TestCase): self.assertTrue( tasks[0].task_spec.name == step_name or tasks[ - 0].task_spec.description == step_name, - 'Expected step %s, got %s (%s)' % (step_name, tasks[0].task_spec.description, tasks[0].task_spec.name)) + 0].task_spec.bpmn_name == step_name, + 'Expected step %s, got %s (%s)' % (step_name, tasks[0].task_spec.bpmn_name, tasks[0].task_spec.name)) if not set_attribs: set_attribs = {} diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/CallActivitySubProcessPropTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/CallActivitySubProcessPropTest.py deleted file mode 100644 index 7a69539b8..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/CallActivitySubProcessPropTest.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- - - - -import sys -import os -import unittest -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) -from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase - -__author__ = 'matth' - - -class CallActivitySubProcessPropTest(BpmnWorkflowTestCase): - """ - Make sure that workflow.data propagates to the subworkflows - in a BPMN - """ - - def setUp(self): - spec, subprocesses = self.load_workflow_spec('proptest-*.bpmn', 'TopLevel') - self.workflow = BpmnWorkflow(spec, subprocesses) - - def testSaveRestore(self): - self.actualTest(True) - - def actualTest(self, save_restore=False): - self.workflow.do_engine_steps() - self.complete_subworkflow() - self.complete_subworkflow() - if save_restore: - self.save_restore() - self.assertTrue(self.workflow.is_completed()) - self.assertEqual(self.workflow.data['valA'],1) - self.assertEqual(self.workflow.data['valB'],1) - self.assertEqual(self.workflow.data['valC'],1) - self.assertEqual(self.workflow.data['valD'],1) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(CallActivitySubProcessPropTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/CallActivityEndEventTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/CallActivityTest.py similarity index 71% rename from SpiffWorkflow/tests/SpiffWorkflow/bpmn/CallActivityEndEventTest.py rename to SpiffWorkflow/tests/SpiffWorkflow/bpmn/CallActivityTest.py index 0dfd32ab9..1c71093c3 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/CallActivityEndEventTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/CallActivityTest.py @@ -1,11 +1,6 @@ -# -*- coding: utf-8 -*- - -import unittest - from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine - from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from SpiffWorkflow.exceptions import WorkflowTaskException +from SpiffWorkflow.bpmn.exceptions import WorkflowTaskException from SpiffWorkflow.task import TaskState from .BpmnWorkflowTestCase import BpmnWorkflowTestCase @@ -18,31 +13,23 @@ class CallActivityTest(BpmnWorkflowTestCase): def setUp(self): self.spec, self.subprocesses = self.load_workflow_spec('call_activity_*.bpmn', 'Process_8200379') - def testRunThroughHappy(self): + def test_data_persists_through_call_activity(self): self.workflow = BpmnWorkflow(self.spec, self.subprocesses) self.workflow.do_engine_steps() + self.complete_subworkflow() + self.assertDictEqual(self.workflow.data, {'pre_var': 'some string', 'my_var': 'World', 'my_other_var': 'Mike'}) - def testCallActivityHasSameScriptEngine(self): - self.runCallActivityWithCustomScript() - - def testCallActivityHasSameScriptEngineAfterSaveRestore(self): - self.runCallActivityWithCustomScript(save_restore=True) - - def runCallActivityWithCustomScript(self, save_restore=False): + def test_call_activity_has_same_script_engine(self): class CustomScriptEngine(PythonScriptEngine): pass - self.workflow = BpmnWorkflow(self.spec, self.subprocesses, - script_engine=CustomScriptEngine()) + self.workflow = BpmnWorkflow(self.spec, self.subprocesses, script_engine=CustomScriptEngine()) self.workflow.do_engine_steps() self.complete_subworkflow() self.assertTrue(self.workflow.is_completed()) self.assertIsInstance(self.workflow.script_engine, CustomScriptEngine) - if save_restore: - self.save_restore() - # Get the subworkflow sub_task = self.workflow.get_tasks_from_spec_name('Sub_Bpmn_Task')[0] sub_workflow = sub_task.workflow @@ -71,7 +58,13 @@ class CallActivityTest(BpmnWorkflowTestCase): task = self.workflow.get_tasks_from_spec_name('Sub_Bpmn_Task')[0] self.assertEqual(task.state, TaskState.ERROR) -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(CallActivityTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) + def test_order_of_tasks_in_get_task_is_call_acitivty_task_first_then_sub_tasks(self): + self.workflow = BpmnWorkflow(self.spec, self.subprocesses) + self.workflow.do_engine_steps() + tasks = self.workflow.get_tasks() + def index_of(name): + return [i for i, x in enumerate(tasks) if x.task_spec.name == name][0] + + self.assertLess(index_of('Activity_Call_Activity'), index_of('Start_Called_Activity')) + self.assertLess(index_of('Activity_Call_Activity'), index_of('Sub_Bpmn_Task')) + self.assertLess(index_of('Activity_Call_Activity'), index_of('End_Called_Activity')) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/CollaborationTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/CollaborationTest.py index b3c03f223..bbbcddd70 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/CollaborationTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/CollaborationTest.py @@ -1,8 +1,8 @@ -from SpiffWorkflow.bpmn.specs.SubWorkflowTask import CallActivity -from SpiffWorkflow.bpmn.workflow import BpmnWorkflow, BpmnMessage +from SpiffWorkflow.bpmn.specs.mixins.subworkflow_task import CallActivity +from SpiffWorkflow.bpmn.workflow import BpmnWorkflow from SpiffWorkflow.task import TaskState -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from .BpmnWorkflowTestCase import BpmnWorkflowTestCase class CollaborationTest(BpmnWorkflowTestCase): @@ -54,7 +54,7 @@ class CollaborationTest(BpmnWorkflowTestCase): # Waiting Events should contain details about what we are no waiting on. events = workflow.waiting_events() self.assertEqual(1, len(events)) - self.assertEqual("Message", events[0]['event_type']) + self.assertEqual("MessageEventDefinition", events[0]['event_type']) self.assertEqual("Love Letter Response", events[0]['name']) self.assertEqual(['lover'], events[0]['value'][0].correlation_keys) self.assertEqual('from_name', events[0]['value'][0].retrieval_expression) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/CustomScriptTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/CustomScriptTest.py index ddd5e143c..5d9744566 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/CustomScriptTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/CustomScriptTest.py @@ -1,12 +1,9 @@ -# -*- coding: utf-8 -*- -import unittest - -from SpiffWorkflow.exceptions import WorkflowTaskException from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine from SpiffWorkflow.bpmn.PythonScriptEngineEnvironment import TaskDataEnvironment from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from SpiffWorkflow.bpmn.exceptions import WorkflowTaskException +from .BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'McDonald, danfunk' @@ -36,11 +33,13 @@ class CustomInlineScriptTest(BpmnWorkflowTestCase): self.actual_test(save_restore=False) def actual_test(self, save_restore): - if save_restore: self.save_restore() + if save_restore: + self.save_restore() self.workflow.do_engine_steps() self.complete_subworkflow() self.complete_subworkflow() - if save_restore: self.save_restore() + if save_restore: + self.save_restore() data = self.workflow.last_task.data self.assertEqual(data['c1'], 'HELLO') self.assertEqual(data['c2'], 'GOODBYE') @@ -54,9 +53,3 @@ class CustomInlineScriptTest(BpmnWorkflowTestCase): self.assertTrue('custom_function' in str(e.exception)) task = self.workflow.get_tasks_from_spec_name('Activity_1y303ko')[0] self.assertEqual(task.state, TaskState.ERROR) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(CustomInlineScriptTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/DataObjectReferenceTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/DataObjectTest.py similarity index 78% rename from SpiffWorkflow/tests/SpiffWorkflow/bpmn/DataObjectReferenceTest.py rename to SpiffWorkflow/tests/SpiffWorkflow/bpmn/DataObjectTest.py index 45b52430f..da85b24c0 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/DataObjectReferenceTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/DataObjectTest.py @@ -1,4 +1,4 @@ -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from .BpmnWorkflowTestCase import BpmnWorkflowTestCase from SpiffWorkflow.bpmn.workflow import BpmnWorkflow from SpiffWorkflow.bpmn.exceptions import WorkflowDataException @@ -87,3 +87,24 @@ class DataObjectReferenceTest(BpmnWorkflowTestCase): self.assertNotIn('obj_1', sp.data) # The update should persist in the main process self.assertEqual(self.workflow.data['obj_1'], 'hello again') + +class DataObjectGatewayTest(BpmnWorkflowTestCase): + + def setUp(self): + spec, subprocesses = self.load_workflow_spec('data_object_gateway.bpmn', 'main') + self.workflow = BpmnWorkflow(spec, subprocesses) + self.workflow.do_engine_steps() + + def testExpression(self): + task = self.workflow.get_ready_user_tasks()[0] + # Set the data object + task.data = {'val': True} + task.run() + # The gateway depends on the value of the data object + self.workflow.do_engine_steps() + self.assertTrue(self.workflow.is_completed()) + completed = [task.task_spec.name for task in self.workflow.get_tasks()] + self.assertIn('yes', completed) + self.assertNotIn('no', completed) + # The data object was removed by the script engine + self.assertNotIn('val', self.workflow.last_task.data) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/DataStoreReferenceTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/DataStoreReferenceTest.py index a9e3d7358..a8b1f4d1c 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/DataStoreReferenceTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/DataStoreReferenceTest.py @@ -1,6 +1,4 @@ -from tests.SpiffWorkflow.bpmn.BpmnLoaderForTests import TestDataStore -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase -from SpiffWorkflow.bpmn.exceptions import WorkflowDataException +from .BpmnWorkflowTestCase import BpmnWorkflowTestCase from SpiffWorkflow.bpmn.workflow import BpmnWorkflow class DataStoreReferenceTest(BpmnWorkflowTestCase): diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/FeelExpressionEngineTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/FeelExpressionEngineTest.py index 474e988db..d7d38da5b 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/FeelExpressionEngineTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/FeelExpressionEngineTest.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- - -import unittest +import datetime from SpiffWorkflow.bpmn.FeelLikeScriptEngine import FeelLikeScriptEngine, FeelInterval from SpiffWorkflow.bpmn.PythonScriptEngineEnvironment import BoxedTaskDataEnvironment -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase -import datetime +from .BpmnWorkflowTestCase import BpmnWorkflowTestCase + __author__ = 'matth' @@ -50,28 +49,3 @@ class FeelExpressionTest(BpmnWorkflowTestCase): for test in tests: self.assertEqual(self.expressionEngine._evaluate(test[0], test[2]), test[1], "test --> %s <-- with variables ==> %s <==Fail!" % (test[0], str(test[2]))) - - def testRunThroughDMNExpression(self): - """ - Real world test - """ - data = { - "exclusive": [ - { - "ExclusiveSpaceAMComputingID": None - } - ] - } - x = self.expressionEngine._evaluate( - """sum([1 for x in exclusive if x.get('ExclusiveSpaceAMComputingID',None)==None])""", - data - ) - self.assertEqual(x, 1) - - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(FeelExpressionTest) - -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/IOSpecTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/IOSpecTest.py index 963fa3368..86775ebaa 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/IOSpecTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/IOSpecTest.py @@ -2,8 +2,7 @@ from SpiffWorkflow.bpmn.exceptions import WorkflowDataException from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow - -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from .BpmnWorkflowTestCase import BpmnWorkflowTestCase class CallActivityDataTest(BpmnWorkflowTestCase): @@ -25,7 +24,7 @@ class CallActivityDataTest(BpmnWorkflowTestCase): with self.assertRaises(WorkflowDataException) as exc: self.advance_to_subprocess() - self.assertEqual(exc.exception.data_input.name,'in_2') + self.assertEqual(exc.exception.data_input.bpmn_id, 'in_2') def testCallActivityMissingOutput(self): @@ -40,7 +39,7 @@ class CallActivityDataTest(BpmnWorkflowTestCase): with self.assertRaises(WorkflowDataException) as exc: self.complete_subprocess() - self.assertEqual(exc.exception.data_output.name, 'out_2') + self.assertEqual(exc.exception.data_output.bpmn_id, 'out_2') def actual_test(self, save_restore=False): @@ -105,7 +104,7 @@ class IOSpecOnTaskTest(BpmnWorkflowTestCase): set_data.script = """in_1, unused = 1, True""" with self.assertRaises(WorkflowDataException) as exc: self.workflow.do_engine_steps() - self.assertEqual(exc.exception.data_input.name, 'in_2') + self.assertEqual(exc.exception.data_input.bpmn_id, 'in_2') def testIOSpecOnTaskMissingOutput(self): self.workflow = BpmnWorkflow(self.spec, self.subprocesses) @@ -114,7 +113,7 @@ class IOSpecOnTaskTest(BpmnWorkflowTestCase): task.data.update({'out_1': 1}) with self.assertRaises(WorkflowDataException) as exc: task.run() - self.assertEqual(exc.exception.data_output.name, 'out_2') + self.assertEqual(exc.exception.data_output.bpmn_id, 'out_2') def actual_test(self, save_restore=False): self.workflow = BpmnWorkflow(self.spec, self.subprocesses) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/InclusiveGatewayTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/InclusiveGatewayTest.py index 1d3b987dc..31f10083c 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/InclusiveGatewayTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/InclusiveGatewayTest.py @@ -1,5 +1,5 @@ from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from SpiffWorkflow.exceptions import WorkflowTaskException +from SpiffWorkflow.bpmn.exceptions import WorkflowTaskException from SpiffWorkflow.task import TaskState from .BpmnWorkflowTestCase import BpmnWorkflowTestCase diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/InvalidProcessIDTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/InvalidProcessIDTest.py deleted file mode 100644 index 5a6996409..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/InvalidProcessIDTest.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- - -import sys -import os -import unittest - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) -from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase - -__author__ = 'essweine' - - -class InvalidProcessIDTest(BpmnWorkflowTestCase): - - def testInvalidWorkflowProcess(self): - self.assertRaisesRegex( - Exception, "The process '\w+' was not found*", - self.load_workflow_spec, "invalid_process*.bpmn", "topworkflow") - - def testInvalidCalledElement(self): - self.assertRaisesRegex( - ValidationException, "The process '\w+' was not found", - self.load_workflow_spec, "invalid_process*.bpmn", "top_workflow") - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(InvalidProcessIDTest) - -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/InvalidWorkflowsTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/InvalidWorkflowsTest.py index 9987567a5..d8d97e45e 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/InvalidWorkflowsTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/InvalidWorkflowsTest.py @@ -1,40 +1,23 @@ # -*- coding: utf-8 -*- - -import unittest - -import os - from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException -from SpiffWorkflow.signavio.parser.bpmn import SignavioBpmnParser -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from .BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'matth' class InvalidWorkflowsTest(BpmnWorkflowTestCase): - def testDisconnectedBoundaryEvent(self): - - with self.assertRaises(ValidationException) as exc: - parser = SignavioBpmnParser() - filename = os.path.join(os.path.dirname(__file__), 'data', 'Invalid-Workflows/Disconnected-Boundary-Event.bpmn20.xml') - parser.add_bpmn_file(filename) - self.assertIn('Intermediate Catch Event has no incoming sequences', str(exc)) - self.assertIn('bpmn:intermediateCatchEvent (id:sid-84C7CE67-D0B6-486A-B097-486DA924FF9D)', str(exc)) - self.assertIn('Invalid-Workflows/Disconnected-Boundary-Event.bpmn20.xml', str(exc)) - def testNoStartEvent(self): try: self.load_workflow_spec( - 'Invalid-Workflows/No-Start-Event.bpmn20.xml', 'No Start Event') - self.fail( - "self.load_workflow_spec('Invalid-Workflows/No-Start-Event.bpmn20.xml', 'No Start Event') should fail.") + 'Invalid-Workflows/No-Start-Event.bpmn20.xml', 'sid-669ddebf-4196-41ee-8b04-bcc90bc5f983') + self.fail("self.load_workflow_spec('Invalid-Workflows/No-Start-Event.bpmn20.xml', 'No Start Event') should fail.") except ValidationException as ex: self.assertTrue('No start event found' in ('%r' % ex), '\'No start event found\' should be a substring of error message: \'%r\'' % ex) self.assertTrue('No-Start-Event.bpmn20.xml' in ex.file_name, '\'No-Start-Event.bpmn20.xml\' should be a substring of error message: \'%r\'' % ex) - def testSubprocessNotFound(self): + def testCallActivityNotFound(self): with self.assertRaises(ValidationException) as exc: self.load_workflow_spec('Invalid-Workflows/Subprocess-Not-Found.bpmn20.xml', 'Subprocess Not Found') @@ -44,14 +27,11 @@ class InvalidWorkflowsTest(BpmnWorkflowTestCase): def testUnsupportedTask(self): try: - self.load_workflow_spec( - 'Invalid-Workflows/Unsupported-Task.bpmn20.xml', 'Unsupported Task') - self.fail( - "self.load_workflow_spec('Invalid-Workflows/Unsupported-Task.bpmn20.xml', 'Unsupported Task') should fail.") + self.load_workflow_spec('Invalid-Workflows/Unsupported-Task.bpmn20.xml', 'sid-00c10a31-5eb4-4f6c-a3eb-3664035ca9a7') + self.fail("self.load_workflow_spec('Invalid-Workflows/Unsupported-Task.bpmn20.xml', 'Unsupported Task') should fail.") except ValidationException as ex: self.assertTrue( - 'There is no support implemented for this task type' in ( - '%r' % ex), + 'There is no support implemented for this task type' in ( '%r' % ex), '\'There is no support implemented for this task type\' should be a substring of error message: \'%r\'' % ex) self.assertTrue('Unsupported-Task.bpmn20.xml' in ex.file_name, '\'Unsupported-Task.bpmn20.xml\' should be a substring of error message: \'%r\'' % ex) @@ -60,8 +40,3 @@ class InvalidWorkflowsTest(BpmnWorkflowTestCase): self.assertTrue('Business Rule Task' in ex.name, '\'Business Rule Task\' should be the name: \'%s\'' % ex.name) - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(InvalidWorkflowsTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/NestedProcessesTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/NestedProcessesTest.py index e3eddf31c..a77579fbe 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/NestedProcessesTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/NestedProcessesTest.py @@ -1,8 +1,6 @@ -import unittest - from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from .BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'neilc' @@ -10,29 +8,61 @@ __author__ = 'neilc' class NestedProcessesTest(BpmnWorkflowTestCase): def setUp(self): - spec, subprocesses = self.load_workflow_spec('Test-Workflows/Nested*.bpmn20.xml', 'Nested Subprocesses') + spec, subprocesses = self.load_workflow_spec( + 'Test-Workflows/Nested*.bpmn20.xml', + 'sid-a12cf1e5-86f4-4d69-9790-6a90342f5963') self.workflow = BpmnWorkflow(spec, subprocesses) def testRunThroughHappy(self): - self.do_next_named_step('Action1') - self.workflow.do_engine_steps() - self.save_restore() + self.complete_task('Action1', True) self.assertEqual(1, len(self.workflow.get_tasks(TaskState.READY))) - self.do_next_named_step('Action2') - self.workflow.do_engine_steps() - self.save_restore() + self.complete_task('Action2', True) self.assertEqual(1, len(self.workflow.get_tasks(TaskState.READY))) - self.do_next_named_step('Action3') + self.complete_task('Action3', True) + self.complete_workflow() + + def testResetToTop(self): + + self.complete_task('Action1', True) + self.complete_task('Action2', True) + self.complete_task('Action3', True) + + task = [t for t in self.workflow.get_tasks() if t.task_spec.bpmn_name == 'Action1'][0] + self.workflow.reset_from_task_id(task.id) + self.assertEqual(task.state, TaskState.READY) + self.assertEqual(len(self.workflow.subprocesses), 0) + task.run() + + self.complete_task('Action2') + self.complete_task('Action3') + self.complete_workflow() + + def testResetToIntermediate(self): + + self.complete_task('Action1', True) + self.complete_task('Action2', True) + self.complete_task('Action3', True) + + task = [t for t in self.workflow.get_tasks() if t.task_spec.bpmn_name == 'Action2'][0] + sub = [t for t in self.workflow.get_tasks() if t.task_spec.bpmn_name == 'Nested level 1'][0] + self.workflow.reset_from_task_id(task.id) + self.assertEqual(task.state, TaskState.READY) + self.assertEqual(sub.state, TaskState.WAITING) + self.assertEqual(len(self.workflow.subprocesses), 1) + task.run() + + self.complete_task('Action3') + self.complete_workflow() + + def complete_task(self, name, save_restore=False): + self.do_next_named_step(name) self.workflow.do_engine_steps() - self.complete_subworkflow() - self.complete_subworkflow() - self.complete_subworkflow() - self.save_restore() - self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) + if save_restore: + self.save_restore() - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(NestedProcessesTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) + def complete_workflow(self): + self.complete_subworkflow() + self.complete_subworkflow() + self.complete_subworkflow() + self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) \ No newline at end of file diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParserTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParserTest.py index 25d26fdb6..b9e7dfbc0 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParserTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParserTest.py @@ -1,44 +1,48 @@ import unittest import os -from SpiffWorkflow.bpmn.parser.BpmnParser import BpmnParser, BpmnValidator +from SpiffWorkflow.bpmn.parser.BpmnParser import BpmnParser from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException class ParserTest(unittest.TestCase): + def setUp(self): + self.parser = BpmnParser() + def testIOSpecification(self): - parser = BpmnParser() bpmn_file = os.path.join(os.path.dirname(__file__), 'data', 'io_spec.bpmn') - parser.add_bpmn_file(bpmn_file) - spec = parser.get_spec('subprocess') + self.parser.add_bpmn_file(bpmn_file) + spec = self.parser.get_spec('subprocess') self.assertEqual(len(spec.io_specification.data_inputs), 2) self.assertEqual(len(spec.io_specification.data_outputs), 2) def testDataReferences(self): - parser = BpmnParser() bpmn_file = os.path.join(os.path.dirname(__file__), 'data', 'data_object.bpmn') - parser.add_bpmn_file(bpmn_file) - spec = parser.get_spec("Process") + self.parser.add_bpmn_file(bpmn_file) + spec = self.parser.get_spec("Process") generate = spec.task_specs['generate_data'] read = spec.task_specs['read_data'] self.assertEqual(len(generate.data_output_associations), 1) - self.assertEqual(generate.data_output_associations[0].name, 'obj_1') + self.assertEqual(generate.data_output_associations[0].bpmn_id, 'obj_1') self.assertEqual(len(read.data_input_associations), 1) - self.assertEqual(read.data_input_associations[0].name, 'obj_1') + self.assertEqual(read.data_input_associations[0].bpmn_id, 'obj_1') - def testValidatorError(self): - parser = BpmnParser(validator=BpmnValidator()) - bpmn_file = os.path.join(os.path.dirname(__file__), 'data', - 'data_object_invalid.bpmn') - errored = False - try: - parser.add_bpmn_file(bpmn_file) - except ValidationException as ex: - errored = True - self.assertEqual(ex.file_name, bpmn_file) - self.assertEqual(14, ex.line_number) - self.assertIn('DataObjectReference_0cm8dnh', str(ex)) - assert(errored, "This should have errored out with a validation exception.") + def testSkipSubprocesses(self): + + bpmn_file = os.path.join(os.path.dirname(__file__), 'data', 'call_activity_end_event.bpmn') + self.parser.add_bpmn_file(bpmn_file) + # The default is to require that call activity specs be included, so this should raise an exception + self.assertRaises(ValidationException, self.parser.get_subprocess_specs, 'Process_8200379') + # When call activity specs are skipped, no exception should be raised + subprocess_specs = self.parser.get_subprocess_specs('Process_8200379', require_call_activity_specs=False) + self.assertDictEqual(subprocess_specs, {'Call_Activity_Get_Data': None}) + + def testInvalidProcessID(self): + bpmn_file = os.path.join(os.path.dirname(__file__), 'data', 'call_activity_end_event.bpmn') + self.parser.add_bpmn_file(bpmn_file) + self.assertRaisesRegex( + ValidationException, "The process '\w+' was not found*", + self.parser.get_spec, "Process") diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ProcessDependencyTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ProcessDependencyTest.py index 86514a034..349e10cc9 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ProcessDependencyTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ProcessDependencyTest.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- import os -import unittest from SpiffWorkflow.camunda.parser.CamundaParser import CamundaParser from SpiffWorkflow.spiff.parser.process import SpiffBpmnParser -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from .BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'danfunk' @@ -28,7 +27,6 @@ class ProcessDependencyTest(BpmnWorkflowTestCase): def actual_test(self, parser): # We ought to test the parsers in the packages they belong to, not here. filename = 'call_activity_nested' - process_name = 'Level1' base_dir = os.path.join(os.path.dirname(__file__), 'data', filename) parser.add_bpmn_file(os.path.join(base_dir, 'call_activity_nested.bpmn')) dependencies = parser.get_dependencies() @@ -46,10 +44,3 @@ class ProcessDependencyTest(BpmnWorkflowTestCase): dependencies = parser.get_dependencies() self.assertEqual(4, len(dependencies)) self.assertIn('Level3', dependencies) - - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(ProcessDependencyTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ProcessParserTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ProcessParserTest.py index 88d03dedc..d70d62833 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ProcessParserTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ProcessParserTest.py @@ -1,8 +1,9 @@ +import io import os import unittest +from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser from SpiffWorkflow.bpmn.parser.BpmnParser import BpmnParser -from SpiffWorkflow.bpmn.parser.ProcessParser import ProcessParser def _process_parser(bpmn_filename, process_id): parser = BpmnParser() @@ -14,11 +15,70 @@ class ProcessParserTest(unittest.TestCase): def testReturnsEmptyListIfNoCallActivities(self): parser = _process_parser("no-tasks.bpmn", "no_tasks") assert parser.called_element_ids() == [] - + def testHandlesSingleCallActivity(self): parser = _process_parser("single_call_activity.bpmn", "Process_p4pfxhq") assert parser.called_element_ids() == ["SingleTask_Process"] - + def testHandlesMultipleCallActivities(self): parser = _process_parser("multiple_call_activities.bpmn", "Process_90mmqlw") assert parser.called_element_ids() == ["Process_sypm122", "Process_diu8ta2", "Process_l14lar1"] + + def testCanAddDmnFromString(self): + parser = BpmnDmnParser() + parser.add_dmn_str(EMPTY_DMN) + assert len(parser.dmn_parsers) > 0 + + def testCanAddDmnFromFileLikeObject(self): + parser = BpmnDmnParser() + parser.add_dmn_io(io.StringIO(EMPTY_DMN)) + assert len(parser.dmn_parsers) > 0 + + def testCanAddBpmnFromString(self): + parser = BpmnParser() + parser.add_bpmn_str(EMPTY_WORKFLOW) + assert parser.get_spec("no_tasks") is not None + + def testCanAddBpmnFromFileLikeObject(self): + parser = BpmnParser() + parser.add_bpmn_io(io.StringIO(EMPTY_WORKFLOW)) + assert parser.get_spec("no_tasks") is not None + + +EMPTY_WORKFLOW = """ + + + + Flow_184umot + + + Flow_184umot + + + + +""" + +EMPTY_DMN = """ + + + + + + + + + + + + +""" diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/PythonScriptEngineEnvironmentTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/PythonScriptEngineEnvironmentTest.py index 58290f4fc..c2117addd 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/PythonScriptEngineEnvironmentTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/PythonScriptEngineEnvironmentTest.py @@ -1,11 +1,12 @@ import json -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine from SpiffWorkflow.bpmn.PythonScriptEngineEnvironment import BasePythonScriptEngineEnvironment, TaskDataEnvironment from SpiffWorkflow.bpmn.workflow import BpmnWorkflow from SpiffWorkflow.task import TaskState +from .BpmnWorkflowTestCase import BpmnWorkflowTestCase + def example_global(): pass diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/PythonScriptEngineTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/PythonScriptEngineTest.py index 05ac0df0c..16b01d880 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/PythonScriptEngineTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/PythonScriptEngineTest.py @@ -1,14 +1,11 @@ # -*- coding: utf-8 -*- -import sys -import os -import unittest - from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine from SpiffWorkflow.bpmn.workflow import BpmnWorkflow +from SpiffWorkflow.bpmn.exceptions import WorkflowTaskException +from SpiffWorkflow.task import TaskState -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from .BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'danfunk' @@ -17,21 +14,39 @@ class PythonScriptEngineTest(BpmnWorkflowTestCase): def setUp(self): self.expressionEngine = PythonScriptEngine() + spec, subprocesses = self.load_workflow_spec('ScriptTest.bpmn', 'Process_1l85e0n') + self. workflow = BpmnWorkflow(spec, subprocesses) - # All this, just so we have a task object, not using anything in the Script. - spec, subprocesses = self.load_workflow_spec('ScriptTest.bpmn', 'ScriptTest') - workflow = BpmnWorkflow(spec, subprocesses) - workflow.do_engine_steps() - self.task = workflow.last_task + def testRunThroughHappy(self): + + self.workflow.do_engine_steps() + data = self.workflow.last_task.data + self.assertEqual(data,{'testvar': {'a': 1, 'b': 2, 'new': 'Test'}, + 'testvar2': [{'x': 1, 'y': 'a'}, + {'x': 2, 'y': 'b'}, + {'x': 3, 'y': 'c'}], + 'sample': ['b', 'c']}) + + def testNoDataPollution(self): + """Ran into an issue where data from one run of a workflow could + bleed into a separate execution. It will think a variable is there + when it should not be there""" + startTask = self.workflow.get_tasks(TaskState.READY)[0] + self.workflow.do_engine_steps() + self.assertTrue(self.workflow.is_completed()) + self.assertTrue("testvar" in self.workflow.last_task.data) + self.assertFalse("testvar" in startTask.data) + + # StartTask doesn't know about testvar, it happened earlier. + # calling an exec that references testvar, in the context of the + # start task should fail. + with self.assertRaises(WorkflowTaskException): + self.workflow.script_engine.evaluate(startTask, 'testvar == True') def testFunctionsAndGlobalsAreRemoved(self): - self.assertIn('testvar', self.task.data) - self.assertIn('testvar2', self.task.data) - self.assertIn('sample', self.task.data) - self.assertNotIn('my_function', self.task.data) - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(PythonScriptEngineTest) - -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) + self.workflow.do_engine_steps() + task = self.workflow.last_task + self.assertIn('testvar', task.data) + self.assertIn('testvar2', task.data) + self.assertIn('sample', task.data) + self.assertNotIn('my_function', task.data) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ResetSubProcessTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ResetSubProcessTest.py index 61a7cc4cc..2a2a80970 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ResetSubProcessTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ResetSubProcessTest.py @@ -1,9 +1,7 @@ # -*- coding: utf-8 -*- -import unittest - from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from .BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'matth' @@ -32,7 +30,7 @@ class ResetSubProcessTest(BpmnWorkflowTestCase): self.actualTest(True) def testResetToOuterWorkflowWhileInSubWorkflow(self): - + self.workflow.do_engine_steps() top_level_task = self.workflow.get_ready_user_tasks()[0] top_level_task.run() @@ -40,8 +38,11 @@ class ResetSubProcessTest(BpmnWorkflowTestCase): task = self.workflow.get_ready_user_tasks()[0] self.save_restore() top_level_task = self.workflow.get_tasks_from_spec_name('Task1')[0] - top_level_task.reset_token({}, reset_data=True) +# top_level_task.reset_token({}, reset_data=True) + self.workflow.reset_from_task_id(top_level_task.id) task = self.workflow.get_ready_user_tasks()[0] + self.assertEqual(len(self.workflow.get_ready_user_tasks()), 1, + "There should only be one task in a ready state.") self.assertEqual(task.get_name(), 'Task1') @@ -78,8 +79,3 @@ class ResetSubProcessTest(BpmnWorkflowTestCase): task.run() self.workflow.do_engine_steps() self.assertTrue(self.workflow.is_completed()) - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(ResetSubProcessTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ResetTokenOnBoundaryEventTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ResetTokenOnBoundaryEventTest.py new file mode 100644 index 000000000..9f36686cf --- /dev/null +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ResetTokenOnBoundaryEventTest.py @@ -0,0 +1,95 @@ +from SpiffWorkflow.bpmn.workflow import BpmnWorkflow +from SpiffWorkflow.task import TaskState +from .BpmnWorkflowTestCase import BpmnWorkflowTestCase + + +class ResetTokenOnBoundaryEventTest(BpmnWorkflowTestCase): + """Assure that when we reset a token to a previous task, and that + task has a boundary event, that the boundary event is reset to the + correct state.""" + + def setUp(self): + spec, subprocesses = self.load_workflow_spec('reset_with_boundary_event.bpmn', 'token') + self.workflow = BpmnWorkflow(spec, subprocesses) + + def testResetToOuterWorkflow(self): + self.reset_to_outer_workflow(save_restore=False) + + def testResetToSubprocess(self): + self.reset_to_subprocess(save_restore=False) + + def testSaveRestore(self): + self.reset_to_outer_workflow(save_restore=True) + + def reset_to_outer_workflow(self, save_restore=False): + + # Advance insie the subworkflow + self.advance_to_task('Last') + sub = self.workflow.get_tasks_from_spec_name('subprocess')[0] + timer_event = self.workflow.get_tasks_from_spec_name('Event_My_Timer')[0] + self.assertEqual(TaskState.CANCELLED, timer_event.state) + + if save_restore: + self.save_restore() + + # Here we reset back to the first task + first = self.workflow.get_tasks_from_spec_name('First')[0] + self.workflow.reset_from_task_id(first.id) + + if save_restore: + self.save_restore() + + # At which point, the timer event should return to a waiting state, the subprocess shoud have been removed + task = self.workflow.get_tasks_from_spec_name('First')[0] + self.assertEqual(task.state, TaskState.READY) + timer_event = self.workflow.get_tasks_from_spec_name('Event_My_Timer')[0] + self.assertEqual(timer_event.state, TaskState.WAITING) + self.assertNotIn(sub.id, self.workflow.subprocesses) + + # Ensure the workflow can be completed without being stuck on stranded tasks + self.complete_workflow() + self.assertTrue(self.workflow.is_completed()) + + def reset_to_subprocess(self, save_restore=False): + + # Advance past the subworkflow + self.advance_to_task('Final') + if save_restore: + self.save_restore() + + # Reset to a task inside the subworkflow + task = self.workflow.get_tasks_from_spec_name('Last')[0] + self.workflow.reset_from_task_id(task.id) + + if save_restore: + self.save_restore() + + # The task we returned to should be ready, the subprocess should be waiting, the final task should be future + sub = self.workflow.get_tasks_from_spec_name('subprocess')[0] + self.assertEqual(sub.state, TaskState.WAITING) + self.assertEqual(task.state, TaskState.READY) + final = self.workflow.get_tasks_from_spec_name('Final')[0] + self.assertEqual(final.state, TaskState.FUTURE) + + # Ensure the workflow can be completed without being stuck on stranded tasks + self.complete_workflow() + self.assertTrue(self.workflow.is_completed()) + + def advance_to_task(self, name): + + self.workflow.do_engine_steps() + ready_tasks = self.workflow.get_tasks(TaskState.READY) + while ready_tasks[0].task_spec.name != name: + ready_tasks[0].run() + self.workflow.do_engine_steps() + self.workflow.refresh_waiting_tasks() + ready_tasks = self.workflow.get_tasks(TaskState.READY) + + def complete_workflow(self): + + ready_tasks = self.workflow.get_tasks(TaskState.READY) + while len(ready_tasks) > 0: + ready_tasks[0].run() + self.workflow.do_engine_steps() + self.workflow.refresh_waiting_tasks() + ready_tasks = self.workflow.get_tasks(TaskState.READY) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/SameNameBugTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/SameNameBugTest.py deleted file mode 100644 index 7ee0907f0..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/SameNameBugTest.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- - -import unittest - -from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine -from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase - -__author__ = 'sartography' - - -class SameNameBugTest(BpmnWorkflowTestCase): - - """Should we bail out with a good error message, when two BPMN diagrams - that work with each other, have tasks with the same id?!?""" - - def setUp(self): - spec, subprocesses = self.load_workflow_spec('same_id*.bpmn', 'same_id') - self.workflow = BpmnWorkflow(spec, subprocesses, script_engine=PythonScriptEngine()) - - def testRunThroughHappy(self): - self.actual_test(save_restore=False) - - def testThroughSaveRestore(self): - self.actual_test(save_restore=True) - - def actual_test(self,save_restore = False): - if save_restore: - self.save_restore() - self.workflow.do_engine_steps() - if save_restore: - self.save_restore() - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(SameNameBugTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ScriptTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ScriptTest.py deleted file mode 100644 index da0a4a29f..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ScriptTest.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- - -import unittest - -from SpiffWorkflow.exceptions import WorkflowTaskException -from SpiffWorkflow.task import TaskState -from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase - -__author__ = 'matth' - - -class InlineScriptTest(BpmnWorkflowTestCase): - - def setUp(self): - spec, subprocesses = self.load_workflow_spec('ScriptTest.bpmn', 'ScriptTest') - self.workflow = BpmnWorkflow(spec, subprocesses) - - def testRunThroughHappy(self): - - self.workflow.do_engine_steps() - data = self.workflow.last_task.data - self.assertEqual(data,{'testvar': {'a': 1, 'b': 2, 'new': 'Test'}, - 'testvar2': [{'x': 1, 'y': 'a'}, - {'x': 2, 'y': 'b'}, - {'x': 3, 'y': 'c'}], - 'sample': ['b', 'c']}) - - def testNoDataPollution(self): - """Ran into an issue where data from one run of a workflow could - bleed into a separate execution. It will think a variable is there - when it should not be there""" - startTask = self.workflow.get_tasks(TaskState.READY)[0] - self.workflow.do_engine_steps() - self.assertTrue(self.workflow.is_completed()) - self.assertTrue("testvar" in self.workflow.last_task.data) - self.assertFalse("testvar" in startTask.data) - - # StartTask doesn't know about testvar, it happened earlier. - # calling an exec that references testvar, in the context of the - # start task should fail. - with self.assertRaises(WorkflowTaskException): - result = self.workflow.script_engine.evaluate(startTask, 'testvar == True') - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(InlineScriptTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ScriptTestBox.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ScriptTestBox.py deleted file mode 100644 index 0303afbd9..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ScriptTestBox.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- - - - -import unittest -from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase - -__author__ = 'matth' - - -class InlineScriptTest(BpmnWorkflowTestCase): - - def setUp(self): - self.spec = self.load_spec() - - def load_spec(self): - return self.load_workflow_spec('ScriptTestBox.bpmn', 'ScriptTest') - - def testRunThroughHappy(self): - - self.workflow = BpmnWorkflow(self.spec) - self.workflow.do_engine_steps() - data = self.workflow.last_task.data - self.assertEqual(data,{'testvar': {'a': 1, 'b': 2, 'new': 'Test'}, - 'testvar2': [{'x': 1, 'y': 'a'}, - {'x': 2, 'y': 'b'}, - {'x': 3, 'y': 'c'}], - 'sample': ['b', 'c'], 'end_event': None}) - - - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(InlineScriptTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/SequentialMultiInstanceTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/SequentialMultiInstanceTest.py index c70b8e473..9a91d4499 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/SequentialMultiInstanceTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/SequentialMultiInstanceTest.py @@ -1,9 +1,8 @@ -from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.exceptions import WorkflowDataException from SpiffWorkflow.bpmn.workflow import BpmnWorkflow from SpiffWorkflow.bpmn.specs.data_spec import TaskDataReference -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from .BpmnWorkflowTestCase import BpmnWorkflowTestCase class BaseTestCase(BpmnWorkflowTestCase): diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ServiceTaskTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ServiceTaskTest.py index a0112efea..60eea1808 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ServiceTaskTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ServiceTaskTest.py @@ -2,13 +2,12 @@ import os import sys import unittest +from SpiffWorkflow.bpmn.workflow import BpmnWorkflow +from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase dirname = os.path.dirname(__file__) sys.path.insert(0, os.path.join(dirname, '..', '..', '..')) -from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine -from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase class ServiceTaskTest(BpmnWorkflowTestCase): diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/SwimLaneTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/SwimLaneTest.py index a7000d526..0721a1409 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/SwimLaneTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/SwimLaneTest.py @@ -59,7 +59,7 @@ class SwimLaneTest(BpmnWorkflowTestCase): self.workflow.run_task_from_id(tasks[0].id) self.workflow.do_engine_steps() tasks = self.workflow.get_ready_user_tasks() - self.assertEqual("SubProcessTask", tasks[0].task_spec.description) + self.assertEqual("SubProcessTask", tasks[0].task_spec.bpmn_name) self.assertEqual(tasks[0].task_spec.lane, "C") def suite(): diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Invalid-Workflows/Disconnected-Boundary-Event.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Invalid-Workflows/Disconnected-Boundary-Event.signavio.xml deleted file mode 100644 index 6c8e6d360..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Invalid-Workflows/Disconnected-Boundary-Event.signavio.xml +++ /dev/null @@ -1,3735 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Disconnected Boundary Event - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Subprocess for - - - Message Non - - - Interrupt SP - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Acknowledge - - - SP Parallel - - - Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Ack Subprocess - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Test Message - - - - - - - - - - - - - - - - - - - - - - - - - This not - actually - connected to - - - the subprocess - task - it's - just - - - on top - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Subprocess for Disconnected Boundary - Event - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Do Something - - - In a Subprocess - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - OK - - - - - - - - - - - - - - - - - - - - OK - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Invalid-Workflows/Multiple-Start-Events.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Invalid-Workflows/Multiple-Start-Events.signavio.xml deleted file mode 100644 index 19fb488b5..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Invalid-Workflows/Multiple-Start-Events.signavio.xml +++ /dev/null @@ -1,3373 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Multiple Start Events - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Acknowledge - - - SP Parallel - - - Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Ack Subprocess - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Do Something - for a - - - While - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Test Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Another thing - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - OK - - - - - - - - - - - - - - - - - - - - OK - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Invalid-Workflows/No-Start-Event.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Invalid-Workflows/No-Start-Event.signavio.xml deleted file mode 100644 index 477f0074f..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Invalid-Workflows/No-Start-Event.signavio.xml +++ /dev/null @@ -1,2404 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - No Start Event - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Acknowledge - - - SP Parallel - - - Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Ack Subprocess - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Do Something - for a - - - While - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Test Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - OK - - - - - - - - - - - - - - - - - - - - OK - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Invalid-Workflows/Recursive-Subprocesses.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Invalid-Workflows/Recursive-Subprocesses.signavio.xml deleted file mode 100644 index 87b91546b..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Invalid-Workflows/Recursive-Subprocesses.signavio.xml +++ /dev/null @@ -1,3428 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Recursive Subprocesses - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Subprocess for - - - Recursive - - - Subprocesses - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Acknowledge - - - SP Parallel - - - Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Ack Subprocess - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Test Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Subprocess for Recursive Subprocesses - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Recursive - - - Subprocesses - - - (callback!) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - OK - - - - - - - - - - - - - - - - - - - - OK - - - - - - - - - - - - - - - - - - - - Done - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Invalid-Workflows/Subprocess-Not-Found.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Invalid-Workflows/Subprocess-Not-Found.signavio.xml deleted file mode 100644 index ed0b8aef4..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Invalid-Workflows/Subprocess-Not-Found.signavio.xml +++ /dev/null @@ -1,3565 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Subprocess Not Found - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Subprocess for - - - Subprocess Not - - - Found - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Acknowledge - - - SP Parallel - - - Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Ack Subprocess - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Test Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Subprocess for Subprocess Not Found - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Do Something - - - In a Subprocess - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - OK - - - - - - - - - - - - - - - - - - - - OK - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Invalid-Workflows/Unsupported-Task.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Invalid-Workflows/Unsupported-Task.signavio.xml deleted file mode 100644 index 6270330fd..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Invalid-Workflows/Unsupported-Task.signavio.xml +++ /dev/null @@ -1,3182 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Unsupported Task - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Acknowledge - - - SP Parallel - - - Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Ack Subprocess - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Do Something - for a - - - While - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Test Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Business Rule - - - Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - OK - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - OK - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/ScriptTestBox.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/ScriptTestBox.bpmn deleted file mode 100644 index 050c2e38e..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/ScriptTestBox.bpmn +++ /dev/null @@ -1,56 +0,0 @@ - - - - - Flow_0dsbqk4 - - - - Flow_0dsbqk4 - Flow_1izwhjx - testvar = {'a':1,'b':2} -testvar2 = [{'x':1,'y':'a'}, - {'x':2,'y':'b'}, - {'x':3,'y':'c'}] - - - Flow_1rbktuo - - - - - Flow_1izwhjx - Flow_1rbktuo - testvar.new = 'Test' -sample = [x.y for x in testvar2 if x.x > 1] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Action-Management.bpmn20.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Action-Management.bpmn20.xml index 85d39b530..56a73c7c6 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Action-Management.bpmn20.xml +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Action-Management.bpmn20.xml @@ -1,421 +1,454 @@ - - - - - - - - - - - - - - - - - - - - sid-A8FDFF7F-AAEC-4FE2-A261-DC46CCE8A925 - sid-32FA774A-C8E2-4ED8-92AB-262BB2309EDC - sid-A7A0FBEC-D736-45F1-A16F-45318E27874F - sid-C077D304-0ED5-47D8-B9EA-9D390CA0F86A - sid-CBBEEC71-BFE5-48BA-875D-2D83042C2491 - sid-2C3EA718-3768-4E1B-AB38-44001C090FEE - sid-2D13DB20-B41B-44C7-BC3B-ECE223C8B793 - sid-2ABC7FB8-98FF-4BE6-8A7F-941CBDEE3232 - - - - - - sid-09C66072-F204-4E32-A501-80D7BD2F45E7 - sid-3D825928-1108-4E19-907F-E6B9ADA5BB3B - sid-F2919669-F486-4C9A-B80E-6EDE1876962A - sid-CE3659E8-9ACC-4449-AD65-C5CF2DCB8054 - sid-07366683-F441-49D4-8A06-7C3D78CCEDE4 - sid-EC95E155-147C-4FD1-AFE5-5B803F655E3B - - - - - - - sid-16CDDB98-02B8-4DB8-8433-09A1F0170561 - - - Some documentation - - - - - - sid-16CDDB98-02B8-4DB8-8433-09A1F0170561 - sid-D43E1CF5-8963-434E-9F2D-64C5ABE362A7 - - - Some docs on a gateway - - - - sid-D43E1CF5-8963-434E-9F2D-64C5ABE362A7 - sid-880EE112-139E-4533-BB98-8A1E6D943A18 - sid-4B3460BF-1433-4961-BEA6-CD4766A5F509 - - - - - - sid-C06ACF4A-E241-4E40-B283-F35060801420 - sid-3FB96CF0-BCC8-427E-9142-9F7C72F07893 - sid-8CA0DD43-CC77-424E-B98D-BEBDBA7F8E85 - - - - - - - - sid-3FB96CF0-BCC8-427E-9142-9F7C72F07893 - sid-CA558A2D-1F6E-4BEB-B04F-6868529FCC24 - - - - - - sid-880EE112-139E-4533-BB98-8A1E6D943A18 - sid-6B67CBBC-E314-4DCF-B12F-968AEA30B05D - - - - - - - - - sid-4B3460BF-1433-4961-BEA6-CD4766A5F509 - sid-C06ACF4A-E241-4E40-B283-F35060801420 - - - - - - - - - sid-CA558A2D-1F6E-4BEB-B04F-6868529FCC24 - sid-6B67CBBC-E314-4DCF-B12F-968AEA30B05D - - - - - - - - sid-FFC6AFF7-1730-4FA8-BAE1-D5AE564FB8FF - sid-D40873A8-FA42-4FA8-BC6E-74B84D57C045 - - - - - - - - sid-2F70B74A-5D28-4D73-9C3B-540E7F9723F2 - sid-2DE53FE2-6F50-4EF5-9B11-E3733E2BD494 - - - - - - - sid-2DE53FE2-6F50-4EF5-9B11-E3733E2BD494 - - - - - - sid-D40873A8-FA42-4FA8-BC6E-74B84D57C045 - - - - sid-2F70B74A-5D28-4D73-9C3B-540E7F9723F2 - - finish_time - - - - - - - sid-8CA0DD43-CC77-424E-B98D-BEBDBA7F8E85 - sid-FFC6AFF7-1730-4FA8-BAE1-D5AE564FB8FF - - start_time - - - - - - - - - - - - - - - Some docs on a default sequence - - - Some docs on a sequence - - - - - - - - - sid-078306CD-6A3E-4B8E-9111-AD0717106A65 - sid-30A09A8A-4BC2-4303-A1C9-1C6EC7BCC039 - sid-7D103D68-E179-4138-9655-FC1ECFC7B897 - sid-42505FB2-8D9D-482D-8528-884BE441786D - sid-661CF7E6-4A5A-42CA-9C6A-6EE7D60DA7B4 - sid-CB483F12-E787-4180-AAB3-B4E74540158C - - - - - - - sid-6A80BDD6-95D5-4072-B6D1-3145C3308B16 - - - - - - - - sid-6A80BDD6-95D5-4072-B6D1-3145C3308B16 - sid-CEBA0A2D-CE09-41E9-B5C0-4741AF63CB25 - - - - - - - - sid-FECB9005-5026-4A3A-A795-7CFBDEA2D9A4 - sid-CEBA0A2D-CE09-41E9-B5C0-4741AF63CB25 - sid-17FC6A7F-4734-4A00-80DA-5C024783D1CF - - - - - - sid-17FC6A7F-4734-4A00-80DA-5C024783D1CF - sid-4023B856-5C8C-4F4B-89CF-00D224EADEDC - sid-5AE8BBED-8AD1-41A8-95DB-4897F73E7492 - - - - - - - - sid-5AE8BBED-8AD1-41A8-95DB-4897F73E7492 - sid-FECB9005-5026-4A3A-A795-7CFBDEA2D9A4 - - - - - - sid-4023B856-5C8C-4F4B-89CF-00D224EADEDC - - - - - - - - - - This task does not have a type on purpose. I assume if it is left off, then it is a user task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + sid-09C66072-F204-4E32-A501-80D7BD2F45E7 + sid-3D825928-1108-4E19-907F-E6B9ADA5BB3B + sid-F2919669-F486-4C9A-B80E-6EDE1876962A + sid-CE3659E8-9ACC-4449-AD65-C5CF2DCB8054 + sid-EC95E155-147C-4FD1-AFE5-5B803F655E3B + sid-07366683-F441-49D4-8A06-7C3D78CCEDE4 + + + + + + sid-A8FDFF7F-AAEC-4FE2-A261-DC46CCE8A925 + sid-32FA774A-C8E2-4ED8-92AB-262BB2309EDC + sid-A7A0FBEC-D736-45F1-A16F-45318E27874F + sid-C077D304-0ED5-47D8-B9EA-9D390CA0F86A + sid-CBBEEC71-BFE5-48BA-875D-2D83042C2491 + sid-2C3EA718-3768-4E1B-AB38-44001C090FEE + sid-2D13DB20-B41B-44C7-BC3B-ECE223C8B793 + sid-2ABC7FB8-98FF-4BE6-8A7F-941CBDEE3232 + + + + + + + sid-16CDDB98-02B8-4DB8-8433-09A1F0170561 + + + Some documentation + + + + + + sid-16CDDB98-02B8-4DB8-8433-09A1F0170561 + sid-D43E1CF5-8963-434E-9F2D-64C5ABE362A7 + + + Some docs on a gateway + + + + sid-D43E1CF5-8963-434E-9F2D-64C5ABE362A7 + sid-880EE112-139E-4533-BB98-8A1E6D943A18 + sid-4B3460BF-1433-4961-BEA6-CD4766A5F509 + + + + + + sid-C06ACF4A-E241-4E40-B283-F35060801420 + sid-3FB96CF0-BCC8-427E-9142-9F7C72F07893 + sid-8CA0DD43-CC77-424E-B98D-BEBDBA7F8E85 + + + + + + + + sid-3FB96CF0-BCC8-427E-9142-9F7C72F07893 + sid-CA558A2D-1F6E-4BEB-B04F-6868529FCC24 + + + + + + sid-880EE112-139E-4533-BB98-8A1E6D943A18 + sid-6B67CBBC-E314-4DCF-B12F-968AEA30B05D + + + + + + + + + sid-4B3460BF-1433-4961-BEA6-CD4766A5F509 + sid-C06ACF4A-E241-4E40-B283-F35060801420 + + + + + + + + + sid-CA558A2D-1F6E-4BEB-B04F-6868529FCC24 + sid-6B67CBBC-E314-4DCF-B12F-968AEA30B05D + + + + + + + + sid-FFC6AFF7-1730-4FA8-BAE1-D5AE564FB8FF + sid-D40873A8-FA42-4FA8-BC6E-74B84D57C045 + + + + + + + + sid-2F70B74A-5D28-4D73-9C3B-540E7F9723F2 + sid-2DE53FE2-6F50-4EF5-9B11-E3733E2BD494 + + + + + + + sid-2DE53FE2-6F50-4EF5-9B11-E3733E2BD494 + + + + + + sid-D40873A8-FA42-4FA8-BC6E-74B84D57C045 + + + + + + + sid-8CA0DD43-CC77-424E-B98D-BEBDBA7F8E85 + sid-FFC6AFF7-1730-4FA8-BAE1-D5AE564FB8FF + + start_time + + + + sid-2F70B74A-5D28-4D73-9C3B-540E7F9723F2 + + finish_time + + + + Some docs on a sequence + + + Some docs on a default sequence + + + + + + + + + + + + + + + + + + + + sid-078306CD-6A3E-4B8E-9111-AD0717106A65 + sid-30A09A8A-4BC2-4303-A1C9-1C6EC7BCC039 + sid-7D103D68-E179-4138-9655-FC1ECFC7B897 + sid-42505FB2-8D9D-482D-8528-884BE441786D + sid-661CF7E6-4A5A-42CA-9C6A-6EE7D60DA7B4 + sid-CB483F12-E787-4180-AAB3-B4E74540158C + + + + + + + sid-6A80BDD6-95D5-4072-B6D1-3145C3308B16 + + + + + + + + sid-6A80BDD6-95D5-4072-B6D1-3145C3308B16 + sid-CEBA0A2D-CE09-41E9-B5C0-4741AF63CB25 + + + + + + + + sid-FECB9005-5026-4A3A-A795-7CFBDEA2D9A4 + sid-CEBA0A2D-CE09-41E9-B5C0-4741AF63CB25 + sid-17FC6A7F-4734-4A00-80DA-5C024783D1CF + + + + + + sid-17FC6A7F-4734-4A00-80DA-5C024783D1CF + sid-4023B856-5C8C-4F4B-89CF-00D224EADEDC + sid-5AE8BBED-8AD1-41A8-95DB-4897F73E7492 + + + + + + + + sid-5AE8BBED-8AD1-41A8-95DB-4897F73E7492 + sid-FECB9005-5026-4A3A-A795-7CFBDEA2D9A4 + + + + + + sid-4023B856-5C8C-4F4B-89CF-00D224EADEDC + + + + + + + + + This task does not have a type on purpose. I assume if it is left off, then it is a user task + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Action-Management.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Action-Management.signavio.xml deleted file mode 100644 index a53e48590..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Action-Management.signavio.xml +++ /dev/null @@ -1,8126 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Action Management - - - - - - - - - - - - - - - - - - - - - undefined - - - Responsible Manager - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Review Action - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Cancel Action - - - (if necessary) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Cancelled - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Notify - - - Responsible - - - Person: New - - - Action - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Notify - - - Responsible - - - Person: Action - - - Cancelled - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - undefined - - - Responsible Person - - - - - - - - - - - - - - - - - - - - - - Do Work - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Overdue - - - Escalation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Complete - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Finish Time - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Start Time - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Do Work - - - - - - - - - - - - - - - - - - - - - undefined - - - Responsible Person - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Start Work - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Complete Work - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Resume Work - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This task does - not - - - have a type on - - - purpose. I - assume - - - if it is left - off, then - - - it is a user - task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Done - - - - - - - - - - - - - - - - - - - - Put On Hold - - - - - - - - - - - - - - - - - - - - Resume - - - - - - - - - - - - - - - - - - - - Start - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Cancel - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Cancel Action - - - - - - - - - - - - - - - - - - - - Approve - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Interrupts-SP.bpmn20.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Interrupts-SP.bpmn20.xml index faec45b3f..b118cc443 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Interrupts-SP.bpmn20.xml +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Interrupts-SP.bpmn20.xml @@ -1,181 +1,181 @@ - - - - - - - - - - - - - - - - - - - - sid-516421BF-6D56-4D23-87A5-6E8FC1E2636F - sid-617B0E1F-42DB-4D40-9B4C-ED631BF6E43A - sid-001BB515-2DC3-47C1-8122-3E853B3FDC34 - sid-4B320727-A78F-47E7-98CF-F589994A1C64 - sid-84C7CE67-D0B6-486A-B097-486DA924FF9D - sid-2BCA4F40-F885-4E59-9646-24E458BBC873 - - - - - - - sid-F1DA0EEB-68EF-47E3-A6EA-5E2D25B6B34D - - - - - - - sid-F1DA0EEB-68EF-47E3-A6EA-5E2D25B6B34D - sid-3204DA36-1068-4F48-99DD-1DBAC909A08B - - - - - - sid-E0F8D63F-0B79-4CDF-9025-F3D40E581A2C - sid-8ABF0F70-8A8A-49F2-8C3E-FC595AB764C6 - - - - - - sid-2E189291-49E9-41CF-8865-D7B3D57D4464 - sid-8ABF0F70-8A8A-49F2-8C3E-FC595AB764C6 - - - sid-2E189291-49E9-41CF-8865-D7B3D57D4464 - - - - - - - sid-3204DA36-1068-4F48-99DD-1DBAC909A08B - sid-E0F8D63F-0B79-4CDF-9025-F3D40E581A2C - - - - - - - - - - - - - - sid-7ED4D4F6-491F-4317-A37D-51C86F911524 - sid-137E71C6-FE26-418B-AFC0-1083027370F7 - sid-D99DD91F-8C51-4913-872A-DBBB5C7BE66C - - - - - - - sid-9D819E98-CD45-48A5-9F8D-B36047118934 - - - - - - sid-9D819E98-CD45-48A5-9F8D-B36047118934 - sid-04951D4B-FEAF-4D8E-924F-6B14AB63B83B - - - - - - sid-04951D4B-FEAF-4D8E-924F-6B14AB63B83B - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + sid-516421BF-6D56-4D23-87A5-6E8FC1E2636F + sid-617B0E1F-42DB-4D40-9B4C-ED631BF6E43A + sid-001BB515-2DC3-47C1-8122-3E853B3FDC34 + sid-4B320727-A78F-47E7-98CF-F589994A1C64 + sid-84C7CE67-D0B6-486A-B097-486DA924FF9D + sid-2BCA4F40-F885-4E59-9646-24E458BBC873 + + + + + + + sid-F1DA0EEB-68EF-47E3-A6EA-5E2D25B6B34D + + + + + + + sid-F1DA0EEB-68EF-47E3-A6EA-5E2D25B6B34D + sid-3204DA36-1068-4F48-99DD-1DBAC909A08B + + + + + + sid-E0F8D63F-0B79-4CDF-9025-F3D40E581A2C + sid-8ABF0F70-8A8A-49F2-8C3E-FC595AB764C6 + + + + + + sid-2E189291-49E9-41CF-8865-D7B3D57D4464 + sid-8ABF0F70-8A8A-49F2-8C3E-FC595AB764C6 + + + sid-2E189291-49E9-41CF-8865-D7B3D57D4464 + + + + + + + sid-3204DA36-1068-4F48-99DD-1DBAC909A08B + sid-E0F8D63F-0B79-4CDF-9025-F3D40E581A2C + + + + + + + + + + + + + + sid-7ED4D4F6-491F-4317-A37D-51C86F911524 + sid-137E71C6-FE26-418B-AFC0-1083027370F7 + sid-D99DD91F-8C51-4913-872A-DBBB5C7BE66C + + + + + + + sid-9D819E98-CD45-48A5-9F8D-B36047118934 + + + + + + sid-9D819E98-CD45-48A5-9F8D-B36047118934 + sid-04951D4B-FEAF-4D8E-924F-6B14AB63B83B + + + + + + sid-04951D4B-FEAF-4D8E-924F-6B14AB63B83B + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Interrupts-SP.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Interrupts-SP.signavio.xml deleted file mode 100644 index 5b5e89409..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Interrupts-SP.signavio.xml +++ /dev/null @@ -1,3566 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Message Interrupts SP - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Subprocess for - - - Message - Interrupts - - - SP - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Acknowledge - - - SP Interrupt - - - Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Test Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Ack Subprocess - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Subprocess for Message Interrupts SP - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Do Something - - - In a Subprocess - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - OK - - - - - - - - - - - - - - - - - - - - OK - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Interrupts.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Interrupts.signavio.xml deleted file mode 100644 index 3c1f39c5f..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Interrupts.signavio.xml +++ /dev/null @@ -1,2005 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Message Interrupts - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Do Something - That - - - Takes A Long - Time - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Acknowledge - - - Interrupt - - - Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Test Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - OK - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Non-Interrupt-SP.bpmn20.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Non-Interrupt-SP.bpmn20.xml index b2c4e6caf..3e930c125 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Non-Interrupt-SP.bpmn20.xml +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Non-Interrupt-SP.bpmn20.xml @@ -1,181 +1,181 @@ - - - - - - - - - - - - - - - - - - - - sid-516421BF-6D56-4D23-87A5-6E8FC1E2636F - sid-617B0E1F-42DB-4D40-9B4C-ED631BF6E43A - sid-001BB515-2DC3-47C1-8122-3E853B3FDC34 - sid-4B320727-A78F-47E7-98CF-F589994A1C64 - sid-2BCA4F40-F885-4E59-9646-24E458BBC873 - sid-84C7CE67-D0B6-486A-B097-486DA924FF9D - - - - - - - sid-F1DA0EEB-68EF-47E3-A6EA-5E2D25B6B34D - - - - - - - sid-F1DA0EEB-68EF-47E3-A6EA-5E2D25B6B34D - sid-3204DA36-1068-4F48-99DD-1DBAC909A08B - - - - - - sid-E0F8D63F-0B79-4CDF-9025-F3D40E581A2C - sid-8ABF0F70-8A8A-49F2-8C3E-FC595AB764C6 - - - - - - sid-2E189291-49E9-41CF-8865-D7B3D57D4464 - sid-8ABF0F70-8A8A-49F2-8C3E-FC595AB764C6 - - - - - - sid-3204DA36-1068-4F48-99DD-1DBAC909A08B - sid-E0F8D63F-0B79-4CDF-9025-F3D40E581A2C - - - sid-2E189291-49E9-41CF-8865-D7B3D57D4464 - - - - - - - - - - - - - - - sid-7ED4D4F6-491F-4317-A37D-51C86F911524 - sid-137E71C6-FE26-418B-AFC0-1083027370F7 - sid-D99DD91F-8C51-4913-872A-DBBB5C7BE66C - - - - - - - sid-9D819E98-CD45-48A5-9F8D-B36047118934 - - - - - - sid-9D819E98-CD45-48A5-9F8D-B36047118934 - sid-04951D4B-FEAF-4D8E-924F-6B14AB63B83B - - - - - - sid-04951D4B-FEAF-4D8E-924F-6B14AB63B83B - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + sid-516421BF-6D56-4D23-87A5-6E8FC1E2636F + sid-617B0E1F-42DB-4D40-9B4C-ED631BF6E43A + sid-001BB515-2DC3-47C1-8122-3E853B3FDC34 + sid-4B320727-A78F-47E7-98CF-F589994A1C64 + sid-2BCA4F40-F885-4E59-9646-24E458BBC873 + sid-84C7CE67-D0B6-486A-B097-486DA924FF9D + + + + + + + sid-F1DA0EEB-68EF-47E3-A6EA-5E2D25B6B34D + + + + + + + sid-F1DA0EEB-68EF-47E3-A6EA-5E2D25B6B34D + sid-3204DA36-1068-4F48-99DD-1DBAC909A08B + + + + + + sid-E0F8D63F-0B79-4CDF-9025-F3D40E581A2C + sid-8ABF0F70-8A8A-49F2-8C3E-FC595AB764C6 + + + + + + sid-2E189291-49E9-41CF-8865-D7B3D57D4464 + sid-8ABF0F70-8A8A-49F2-8C3E-FC595AB764C6 + + + + + + sid-3204DA36-1068-4F48-99DD-1DBAC909A08B + sid-E0F8D63F-0B79-4CDF-9025-F3D40E581A2C + + + sid-2E189291-49E9-41CF-8865-D7B3D57D4464 + + + + + + + + + + + + + + + sid-7ED4D4F6-491F-4317-A37D-51C86F911524 + sid-137E71C6-FE26-418B-AFC0-1083027370F7 + sid-D99DD91F-8C51-4913-872A-DBBB5C7BE66C + + + + + + + sid-9D819E98-CD45-48A5-9F8D-B36047118934 + + + + + + sid-9D819E98-CD45-48A5-9F8D-B36047118934 + sid-04951D4B-FEAF-4D8E-924F-6B14AB63B83B + + + + + + sid-04951D4B-FEAF-4D8E-924F-6B14AB63B83B + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Non-Interrupt-SP.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Non-Interrupt-SP.signavio.xml deleted file mode 100644 index 0a56bd2ae..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Non-Interrupt-SP.signavio.xml +++ /dev/null @@ -1,3576 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Message Non Interrupt SP - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Subprocess for - - - Message Non - - - Interrupt SP - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Outer End - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Acknowledge - - - SP Parallel - - - Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Ack Subprocess - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Test Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Subprocess for Message Non Interrupt SP - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Do Something - - - In a Subprocess - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Inner End - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - OK - - - - - - - - - - - - - - - - - - - - OK - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Non-Interrupt.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Non-Interrupt.signavio.xml deleted file mode 100644 index 6bca2a5b0..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Message-Non-Interrupt.signavio.xml +++ /dev/null @@ -1,2005 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Message Non Interrupt - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Do Something - That - - - Takes A Long - Time - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Acknowledge - - - Non-Interrupt - - - Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Test Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - OK - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Messages.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Messages.signavio.xml deleted file mode 100644 index f04f6ef58..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Messages.signavio.xml +++ /dev/null @@ -1,814 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Messages - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Test Message - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Nested-Sub-level1.bpmn20.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Nested-Sub-level1.bpmn20.xml index 8fb42e2be..9b91153ea 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Nested-Sub-level1.bpmn20.xml +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Nested-Sub-level1.bpmn20.xml @@ -1,96 +1,105 @@ - - - - - - - - - - - - - - - sid-23EF7D0F-BC6E-45ED-A47D-22CEBCE0BE5A - sid-64E2EF25-F986-4834-8C3B-C3533746113E - sid-1B59DCD2-83A0-4687-B7BE-83625395572E - sid-BC014079-199F-4720-95CD-244B0ACB6DE1 - - - - - - - sid-27BA998B-1FEE-4CBA-86D5-8C5968F1478D - - - - - - sid-B8192BBF-7DB4-4AA1-8990-5017C30130A8 - - - - - - - - - sid-27BA998B-1FEE-4CBA-86D5-8C5968F1478D - sid-02F2C617-DFEE-44AE-AAED-145AF2E2D946 - - - - - - - sid-02F2C617-DFEE-44AE-AAED-145AF2E2D946 - sid-B8192BBF-7DB4-4AA1-8990-5017C30130A8 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + sid-23EF7D0F-BC6E-45ED-A47D-22CEBCE0BE5A + sid-64E2EF25-F986-4834-8C3B-C3533746113E + sid-1B59DCD2-83A0-4687-B7BE-83625395572E + sid-BC014079-199F-4720-95CD-244B0ACB6DE1 + + + + + + + sid-27BA998B-1FEE-4CBA-86D5-8C5968F1478D + + + + + + sid-B8192BBF-7DB4-4AA1-8990-5017C30130A8 + + + + + + + + + sid-27BA998B-1FEE-4CBA-86D5-8C5968F1478D + sid-02F2C617-DFEE-44AE-AAED-145AF2E2D946 + + + + + + + sid-02F2C617-DFEE-44AE-AAED-145AF2E2D946 + sid-B8192BBF-7DB4-4AA1-8990-5017C30130A8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Nested-Sub-level1.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Nested-Sub-level1.signavio.xml deleted file mode 100644 index cdab4ebf2..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Nested-Sub-level1.signavio.xml +++ /dev/null @@ -1,1614 +0,0 @@ - - -First sublevel of nested subprocesses -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Nested level 1 - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Action2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Nested level 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Finish - - - - - - - - - - - - - - - - - - - - ToLevel2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Nested-Sub-level2.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Nested-Sub-level2.signavio.xml deleted file mode 100644 index 0a5b40a49..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Nested-Sub-level2.signavio.xml +++ /dev/null @@ -1,1150 +0,0 @@ - - -2nd nested subprocess level -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Nested level 2 - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Action3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Finish - - - - - - - - - - - - - - - - - - - - In - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Nested-Subprocesses.bpmn20.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Nested-Subprocesses.bpmn20.xml index a3ca3c467..839c72827 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Nested-Subprocesses.bpmn20.xml +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Nested-Subprocesses.bpmn20.xml @@ -1,94 +1,100 @@ - - - - - - - - - - - - - - - sid-093DC600-6F99-40CE-988C-7AD87B792F90 - sid-EF0FA50B-FE9B-4C86-9981-4F6B62387D38 - sid-FECD237F-6ABD-4A51-BB9C-B0C7D991202B - sid-C014B4B9-889F-4EE9-9949-C89502C35CF0 - - - - - - - sid-E35CEC65-EA3C-4C5A-BC90-8C17016C24E5 - - - - - - sid-85D2E5A5-BD56-4650-B715-3B6E0BE33443 - - - - - - - - - sid-E35CEC65-EA3C-4C5A-BC90-8C17016C24E5 - sid-5BC5ECB5-884B-449A-AC67-B9B7ED296728 - - - - - - - sid-5BC5ECB5-884B-449A-AC67-B9B7ED296728 - sid-85D2E5A5-BD56-4650-B715-3B6E0BE33443 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + sid-093DC600-6F99-40CE-988C-7AD87B792F90 + sid-EF0FA50B-FE9B-4C86-9981-4F6B62387D38 + sid-FECD237F-6ABD-4A51-BB9C-B0C7D991202B + sid-C014B4B9-889F-4EE9-9949-C89502C35CF0 + + + + + + + sid-E35CEC65-EA3C-4C5A-BC90-8C17016C24E5 + + + + + + sid-85D2E5A5-BD56-4650-B715-3B6E0BE33443 + + + + + + + + + sid-E35CEC65-EA3C-4C5A-BC90-8C17016C24E5 + sid-5BC5ECB5-884B-449A-AC67-B9B7ED296728 + + + + + + + sid-5BC5ECB5-884B-449A-AC67-B9B7ED296728 + sid-85D2E5A5-BD56-4650-B715-3B6E0BE33443 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Nested-Subprocesses.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Nested-Subprocesses.signavio.xml deleted file mode 100644 index 38f1507ac..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Nested-Subprocesses.signavio.xml +++ /dev/null @@ -1,1576 +0,0 @@ - - -Used to test multiple nestings of subprocesses -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Nested Subprocesses - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Action1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Nested level 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Finished - - - - - - - - - - - - - - - - - - - - Continue - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Join-Long-Inclusive.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Join-Long-Inclusive.signavio.xml deleted file mode 100644 index 7ed9ce015..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Join-Long-Inclusive.signavio.xml +++ /dev/null @@ -1,18834 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Parallel Join Long Inclusive - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 5 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 6 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 7 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 8 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 9 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 10 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 11 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 12 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 5 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 6 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 7 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 8 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 9 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 10 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 11 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 12 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - - - - Choose - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - No - - - Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - - - - Choose - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - No - - - Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Yes - - - - - - - - - - - - - - - - - - - - No - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Yes - - - - - - - - - - - - - - - - - - - - No - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Join-Long.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Join-Long.signavio.xml deleted file mode 100644 index c6c844526..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Join-Long.signavio.xml +++ /dev/null @@ -1,18676 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Parallel Join Long - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 5 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 6 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 7 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 8 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 9 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 10 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 11 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - Task - - - 12 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 5 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 6 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 7 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 8 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 9 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 10 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 11 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - Task - - - 12 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - - - - Choose - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 1 - No - - - Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - - - - Choose - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread 2 - No - - - Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Yes - - - - - - - - - - - - - - - - - - - - No - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Yes - - - - - - - - - - - - - - - - - - - - No - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Looping-After-Join.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Looping-After-Join.signavio.xml deleted file mode 100644 index 8e692dd91..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Looping-After-Join.signavio.xml +++ /dev/null @@ -1,6534 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Parallel Looping After Join - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - First Split - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Join of First - Split - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Retry? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Second Split - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2A - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2B - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Join of Second - Split - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Go - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - No - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Yes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Many-Threads-At-Same-Point-Nested.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Many-Threads-At-Same-Point-Nested.signavio.xml deleted file mode 100644 index 668df8ebf..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Many-Threads-At-Same-Point-Nested.signavio.xml +++ /dev/null @@ -1,8272 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Parallel Many Threads At Same Point - Nested - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Outer Split - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Outer Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Outer End - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SP 1A - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SP 1B - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SP 2A - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SP 2B - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Outer Join 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Outer Join 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SP - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Inner split - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1A - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1B - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Inner Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Inner End - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2A - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2B - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Inner Join 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Inner join 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Many-Threads-At-Same-Point.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Many-Threads-At-Same-Point.signavio.xml deleted file mode 100644 index 3cea57c7f..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Many-Threads-At-Same-Point.signavio.xml +++ /dev/null @@ -1,4000 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Parallel Many Threads At Same Point - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Multiple-Splits-And-Joins.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Multiple-Splits-And-Joins.signavio.xml deleted file mode 100644 index 18fda73ad..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Multiple-Splits-And-Joins.signavio.xml +++ /dev/null @@ -1,7277 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Parallel Multiple Splits And Joins - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2A - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2B - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1A - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1B - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Multiple-Splits.bpmn20.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Multiple-Splits.bpmn20.xml index 2812292a9..2459b38a9 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Multiple-Splits.bpmn20.xml +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Multiple-Splits.bpmn20.xml @@ -1,571 +1,589 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sid-9CAB06E6-EDCF-4193-869A-FE8328E8CBFF - sid-F4CFA154-9281-4579-B117-0859A2BFF7E8 - sid-3D61C6FE-D354-41FF-8544-014FAE7B5C5E - sid-06F36AE5-5FD5-487D-A783-64CEAB2C8A91 - sid-107A993F-6302-4391-9BE2-068C9C7B693B - sid-7FE0DFC7-C85C-4512-AE6D-870C434EB8C1 - sid-BE900A6F-2C7A-4444-8DBB-6320D1BAC713 - sid-C6CAD430-8E26-445B-83C0-A36E23C7E1C1 - sid-2563D3F5-8471-4F7C-BDFD-09C38D094F43 - sid-5744C1B5-7791-4CEC-BE63-25659AF6086F - - - - - - - sid-54E118FA-9A24-434C-9E65-36F9D01FB43D - - - - - - sid-54E118FA-9A24-434C-9E65-36F9D01FB43D - sid-7BFA5A55-E297-40FC-88A6-DF1DA809A12C - sid-1187C872-CED2-4F61-A3A6-917961FBB6A8 - - - - - - sid-43F81872-8CCF-4341-9003-438220FA07F4 - sid-00841997-63BF-4E4B-8BB5-B3A9A15F0123 - sid-B22C3013-E93D-43F9-8445-3C0B98AFF3F6 - sid-369B410B-EA82-4896-91FD-23FFF759494A - - - - - - sid-75D1F9CC-4885-4EC4-BB9C-C24DFDA04F69 - - - - - - - sid-369B410B-EA82-4896-91FD-23FFF759494A - sid-75D1F9CC-4885-4EC4-BB9C-C24DFDA04F69 - - - - - - - sid-A24102E3-7EC7-42BA-AF16-AAA14CA59B80 - sid-00841997-63BF-4E4B-8BB5-B3A9A15F0123 - - - - - - - sid-7BFA5A55-E297-40FC-88A6-DF1DA809A12C - sid-43F81872-8CCF-4341-9003-438220FA07F4 - - - - - - sid-715966B5-1BBA-426A-B1AD-B1600C836447 - sid-A24102E3-7EC7-42BA-AF16-AAA14CA59B80 - sid-BFF577ED-4FCE-4C82-8867-69A604840D18 - - - - - - - sid-BFF577ED-4FCE-4C82-8867-69A604840D18 - sid-B22C3013-E93D-43F9-8445-3C0B98AFF3F6 - - - - - - sid-1187C872-CED2-4F61-A3A6-917961FBB6A8 - sid-715966B5-1BBA-426A-B1AD-B1600C836447 - - - - - - - - - - - - - - - - - - - - sid-9F27C720-DF39-4F98-B982-B8401C522627 - sid-CBC4BA2E-F967-4FFA-930F-9351FA2FE1BC - sid-976144D1-F77F-468A-9AEA-44CFE6FC53AF - sid-5785112F-6652-4A79-8765-274633FF857C - sid-50F141BC-DA33-4115-B0BE-3E2F86170A6F - sid-5B01939E-7383-4B15-8B03-7BAD381D815B - - - - - - - sid-93BCB59F-C250-4A99-9656-A018CB6909D0 - sid-71B40CAD-61D1-40E1-879A-5269B2C537C3 - - - - - - sid-71B40CAD-61D1-40E1-879A-5269B2C537C3 - sid-83DCC02D-1092-457B-A92F-FCC1A69B11DB - sid-B138CBD6-3412-4E12-818A-046DBE4EB268 - - - - - - sid-83DCC02D-1092-457B-A92F-FCC1A69B11DB - sid-3BF11953-04AC-47E5-86DF-A021E14B38BE - - - - - - sid-B138CBD6-3412-4E12-818A-046DBE4EB268 - sid-4BD8D37C-5F6B-4E22-B0EF-207C79D2FF1F - - - - - - sid-4BD8D37C-5F6B-4E22-B0EF-207C79D2FF1F - sid-3BF11953-04AC-47E5-86DF-A021E14B38BE - - - - - - sid-93BCB59F-C250-4A99-9656-A018CB6909D0 - - - - - - - - - - - - - - - sid-B132315A-D2F0-4C56-B9B7-0D653E10D055 - sid-C80BC3FC-1DAA-4891-A532-8CAF546022DB - sid-90F285F4-D9B1-47EB-98A4-4DEC26F5004E - sid-4C5398A4-E1EE-453F-84A4-95E36AB3F52D - sid-172D9479-01F2-4322-8DEE-F64EB67370AC - sid-E9F47FE4-FBA9-48C1-94EB-0414FF62CA4B - - - - - - - sid-D142870D-78CA-4179-A412-D55D995F09C2 - sid-EBAA7EDE-A111-4B75-9049-18086C370093 - - - - - - sid-EBAA7EDE-A111-4B75-9049-18086C370093 - sid-DA5A35BE-DE88-458E-9070-5EAF5E50AC3F - sid-F96F1343-A5F2-443E-AE6A-399839FA5CAD - - - - - - sid-DA5A35BE-DE88-458E-9070-5EAF5E50AC3F - sid-2D95B883-D746-42C4-A63B-E79F94DFFA19 - - - - - - sid-F96F1343-A5F2-443E-AE6A-399839FA5CAD - sid-53FC57E6-73A4-44DF-B8A4-2D040EE730E4 - - - - - - sid-53FC57E6-73A4-44DF-B8A4-2D040EE730E4 - sid-2D95B883-D746-42C4-A63B-E79F94DFFA19 - - - - - - sid-D142870D-78CA-4179-A412-D55D995F09C2 - - - - - - - - - - - - - - - sid-907901B0-15E1-463E-B3F2-FCCF07032D21 - sid-15DE9BF4-7DC1-4AB5-ABCB-2D78586129B4 - sid-D0F64CE9-F2AF-4C30-B131-7C5AFCBD83BE - sid-92F09878-8A0A-4027-A8D7-A0CC968646B7 - sid-9B90B624-729B-4ABC-912F-DEEACF9E0BEE - sid-CA956D63-388B-4F67-84BD-60CB84585701 - - - - - - - sid-53E74B08-8501-45E0-BB17-4F1AFA9695B5 - sid-26FD7AF6-2635-4D7F-B84D-A54BC63D059E - - - - - - sid-26FD7AF6-2635-4D7F-B84D-A54BC63D059E - sid-29205115-BF1F-4D60-AA26-A06F992732A2 - sid-E2D0CBCD-5C99-49FA-A29E-D4B70CFEC942 - - - - - - sid-29205115-BF1F-4D60-AA26-A06F992732A2 - sid-67317F54-919D-4CE2-A590-8BB9A538B282 - - - - - - sid-E2D0CBCD-5C99-49FA-A29E-D4B70CFEC942 - sid-9382889C-EFCE-42B2-A6DC-D29A276D1E86 - - - - - - sid-9382889C-EFCE-42B2-A6DC-D29A276D1E86 - sid-67317F54-919D-4CE2-A590-8BB9A538B282 - - - - - - sid-53E74B08-8501-45E0-BB17-4F1AFA9695B5 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sid-9CAB06E6-EDCF-4193-869A-FE8328E8CBFF + sid-F4CFA154-9281-4579-B117-0859A2BFF7E8 + sid-3D61C6FE-D354-41FF-8544-014FAE7B5C5E + sid-06F36AE5-5FD5-487D-A783-64CEAB2C8A91 + sid-107A993F-6302-4391-9BE2-068C9C7B693B + sid-7FE0DFC7-C85C-4512-AE6D-870C434EB8C1 + sid-BE900A6F-2C7A-4444-8DBB-6320D1BAC713 + sid-C6CAD430-8E26-445B-83C0-A36E23C7E1C1 + sid-2563D3F5-8471-4F7C-BDFD-09C38D094F43 + sid-5744C1B5-7791-4CEC-BE63-25659AF6086F + + + + + + + sid-54E118FA-9A24-434C-9E65-36F9D01FB43D + + + + + + sid-54E118FA-9A24-434C-9E65-36F9D01FB43D + sid-7BFA5A55-E297-40FC-88A6-DF1DA809A12C + sid-1187C872-CED2-4F61-A3A6-917961FBB6A8 + + + + + + sid-43F81872-8CCF-4341-9003-438220FA07F4 + sid-00841997-63BF-4E4B-8BB5-B3A9A15F0123 + sid-B22C3013-E93D-43F9-8445-3C0B98AFF3F6 + sid-369B410B-EA82-4896-91FD-23FFF759494A + + + + + + sid-75D1F9CC-4885-4EC4-BB9C-C24DFDA04F69 + + + + + + + sid-369B410B-EA82-4896-91FD-23FFF759494A + sid-75D1F9CC-4885-4EC4-BB9C-C24DFDA04F69 + + + + + + + sid-A24102E3-7EC7-42BA-AF16-AAA14CA59B80 + sid-00841997-63BF-4E4B-8BB5-B3A9A15F0123 + + + + + + + sid-7BFA5A55-E297-40FC-88A6-DF1DA809A12C + sid-43F81872-8CCF-4341-9003-438220FA07F4 + + + + + + sid-715966B5-1BBA-426A-B1AD-B1600C836447 + sid-A24102E3-7EC7-42BA-AF16-AAA14CA59B80 + sid-BFF577ED-4FCE-4C82-8867-69A604840D18 + + + + + + + sid-BFF577ED-4FCE-4C82-8867-69A604840D18 + sid-B22C3013-E93D-43F9-8445-3C0B98AFF3F6 + + + + + + sid-1187C872-CED2-4F61-A3A6-917961FBB6A8 + sid-715966B5-1BBA-426A-B1AD-B1600C836447 + + + + + + + + + + + + + + + + + + + + sid-9F27C720-DF39-4F98-B982-B8401C522627 + sid-CBC4BA2E-F967-4FFA-930F-9351FA2FE1BC + sid-976144D1-F77F-468A-9AEA-44CFE6FC53AF + sid-5785112F-6652-4A79-8765-274633FF857C + sid-50F141BC-DA33-4115-B0BE-3E2F86170A6F + sid-5B01939E-7383-4B15-8B03-7BAD381D815B + + + + + + + sid-93BCB59F-C250-4A99-9656-A018CB6909D0 + sid-71B40CAD-61D1-40E1-879A-5269B2C537C3 + + + + + + sid-71B40CAD-61D1-40E1-879A-5269B2C537C3 + sid-83DCC02D-1092-457B-A92F-FCC1A69B11DB + sid-B138CBD6-3412-4E12-818A-046DBE4EB268 + + + + + + sid-83DCC02D-1092-457B-A92F-FCC1A69B11DB + sid-3BF11953-04AC-47E5-86DF-A021E14B38BE + + + + + + sid-B138CBD6-3412-4E12-818A-046DBE4EB268 + sid-4BD8D37C-5F6B-4E22-B0EF-207C79D2FF1F + + + + + + sid-4BD8D37C-5F6B-4E22-B0EF-207C79D2FF1F + sid-3BF11953-04AC-47E5-86DF-A021E14B38BE + + + + + + sid-93BCB59F-C250-4A99-9656-A018CB6909D0 + + + + + + + + + + + + + + + sid-B132315A-D2F0-4C56-B9B7-0D653E10D055 + sid-C80BC3FC-1DAA-4891-A532-8CAF546022DB + sid-90F285F4-D9B1-47EB-98A4-4DEC26F5004E + sid-4C5398A4-E1EE-453F-84A4-95E36AB3F52D + sid-172D9479-01F2-4322-8DEE-F64EB67370AC + sid-E9F47FE4-FBA9-48C1-94EB-0414FF62CA4B + + + + + + + sid-D142870D-78CA-4179-A412-D55D995F09C2 + sid-EBAA7EDE-A111-4B75-9049-18086C370093 + + + + + + sid-EBAA7EDE-A111-4B75-9049-18086C370093 + sid-DA5A35BE-DE88-458E-9070-5EAF5E50AC3F + sid-F96F1343-A5F2-443E-AE6A-399839FA5CAD + + + + + + sid-DA5A35BE-DE88-458E-9070-5EAF5E50AC3F + sid-2D95B883-D746-42C4-A63B-E79F94DFFA19 + + + + + + sid-F96F1343-A5F2-443E-AE6A-399839FA5CAD + sid-53FC57E6-73A4-44DF-B8A4-2D040EE730E4 + + + + + + sid-53FC57E6-73A4-44DF-B8A4-2D040EE730E4 + sid-2D95B883-D746-42C4-A63B-E79F94DFFA19 + + + + + + sid-D142870D-78CA-4179-A412-D55D995F09C2 + + + + + + + + + + + + + + + sid-907901B0-15E1-463E-B3F2-FCCF07032D21 + sid-15DE9BF4-7DC1-4AB5-ABCB-2D78586129B4 + sid-D0F64CE9-F2AF-4C30-B131-7C5AFCBD83BE + sid-92F09878-8A0A-4027-A8D7-A0CC968646B7 + sid-9B90B624-729B-4ABC-912F-DEEACF9E0BEE + sid-CA956D63-388B-4F67-84BD-60CB84585701 + + + + + + + sid-53E74B08-8501-45E0-BB17-4F1AFA9695B5 + sid-26FD7AF6-2635-4D7F-B84D-A54BC63D059E + + + + + + sid-26FD7AF6-2635-4D7F-B84D-A54BC63D059E + sid-29205115-BF1F-4D60-AA26-A06F992732A2 + sid-E2D0CBCD-5C99-49FA-A29E-D4B70CFEC942 + + + + + + sid-29205115-BF1F-4D60-AA26-A06F992732A2 + sid-67317F54-919D-4CE2-A590-8BB9A538B282 + + + + + + sid-E2D0CBCD-5C99-49FA-A29E-D4B70CFEC942 + sid-9382889C-EFCE-42B2-A6DC-D29A276D1E86 + + + + + + sid-9382889C-EFCE-42B2-A6DC-D29A276D1E86 + sid-67317F54-919D-4CE2-A590-8BB9A538B282 + + + + + + sid-53E74B08-8501-45E0-BB17-4F1AFA9695B5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Multiple-Splits.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Multiple-Splits.signavio.xml deleted file mode 100644 index 9c525db8b..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Multiple-Splits.signavio.xml +++ /dev/null @@ -1,11634 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Parallel Multiple Splits - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Subprocess - - - With a Choice 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Subprocess - - - With a Choice 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Subprocess - - - With a Choice 3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Do First - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Subprocess With a Choice 2 - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - SP 2 - Choose - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SP 2 - No Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SP 2 - Yes Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Subprocess With a Choice 1 - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - SP 1 - Choose - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SP 1 - No Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SP 1 - Yes Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Subprocess With a Choice 3 - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - SP 3 - Choose - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SP 3 - No Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SP 3 - Yes Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - No - - - - - - - - - - - - - - - - - - - - Yes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - No - - - - - - - - - - - - - - - - - - - - Yes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - No - - - - - - - - - - - - - - - - - - - - Yes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-One-Path-Ends.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-One-Path-Ends.signavio.xml deleted file mode 100644 index bf33da092..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-One-Path-Ends.signavio.xml +++ /dev/null @@ -1,3793 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Parallel One Path Ends - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Parallel Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Choice 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Yes Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Yes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - No - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Then-Exclusive-No-Inclusive.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Then-Exclusive-No-Inclusive.signavio.xml deleted file mode 100644 index fd0583b3c..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Then-Exclusive-No-Inclusive.signavio.xml +++ /dev/null @@ -1,4497 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Parallel Then Exclusive No Inclusive - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Parallel Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Choice 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Yes Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - No Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - No - - - - - - - - - - - - - - - - - - - - Yes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Then-Exclusive.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Then-Exclusive.signavio.xml deleted file mode 100644 index 31f702adb..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Then-Exclusive.signavio.xml +++ /dev/null @@ -1,4291 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Parallel Then Exclusive - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Parallel Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Choice 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Yes Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - No Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - No - - - - - - - - - - - - - - - - - - - - Yes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Through-Same-Task.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Through-Same-Task.signavio.xml deleted file mode 100644 index 45a234a01..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Parallel-Through-Same-Task.signavio.xml +++ /dev/null @@ -1,4289 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Parallel Through Same Task - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Repeated Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Choice 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Yes Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - No Task - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - No - - - - - - - - - - - - - - - - - - - - Yes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Scripts.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Scripts.signavio.xml deleted file mode 100644 index f83180b1b..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Scripts.signavio.xml +++ /dev/null @@ -1,3553 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Scripts - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Permanent - - - Script - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Select Type Of - - - Change - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Temp Script - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Emergency? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Acknowledge - - - Emergency - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Permanent - - - - - - - - - - - - - - - - - - - - Temporary - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Confirm - - - - - - - - - - - - - - - - - - - - No - - - - - - - - - - - - - - - - - - - - Yes - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Test-Workflows.bpmn20.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Test-Workflows.bpmn20.xml index b9ab28f94..fe5add95b 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Test-Workflows.bpmn20.xml +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Test-Workflows.bpmn20.xml @@ -1,5 +1,5 @@ - + @@ -57,7 +57,7 @@ sid-B7DB2642-79A3-4D7B-9CB6-06C4854E4C9D sid-EC51B1C2-2A2D-4515-90CF-9CDCF31A8A44 - + @@ -65,7 +65,7 @@ sid-85975D4B-1DC8-4998-A7DF-6F9C31861EE7 sid-36EA1BBD-FB84-4C19-AEFA-5C731F9C8789 - + @@ -73,7 +73,7 @@ sid-488CD0F1-E280-4BDE-B794-64CAF8C4FCA8 sid-979356AC-A00F-456E-9790-39D512F50D3C - + @@ -81,7 +81,7 @@ sid-ECE4F718-B986-45F1-8B0C-C0C1DAE66DB3 sid-B7DB2642-79A3-4D7B-9CB6-06C4854E4C9D - + @@ -109,6 +109,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -133,77 +204,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Test-Workflows.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Test-Workflows.signavio.xml deleted file mode 100644 index ec2212f5d..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Test-Workflows.signavio.xml +++ /dev/null @@ -1,3570 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Test Workflows - - - - - - - - - - - - - - - - - - - - - undefined - - - Test Runner - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Select Test - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Messages - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Scripts - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Message - - - Interrupts - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Message Non - - - Interrupt - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Messages - - - - - - - - - - - - - - - - - - - - Message Interrupts - - - - - - - - - - - - - - - - - - - - Scripts - - - - - - - - - - - - - - - - - - - - Tests Done - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Message Non Interrupt - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Timer-Intermediate.signavio.xml b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Timer-Intermediate.signavio.xml deleted file mode 100644 index c2cba0688..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/Test-Workflows/Timer-Intermediate.signavio.xml +++ /dev/null @@ -1,818 +0,0 @@ - - - -BPMN 2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Timer Intermediate - - - - - - - - - - - - - - - - - - - - - undefined - - - Tester - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Due Time - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/call_activity_call_activity.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/call_activity_call_activity.bpmn index 84c05f4d7..1ec6956bc 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/call_activity_call_activity.bpmn +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/call_activity_call_activity.bpmn @@ -1,16 +1,16 @@ - + - + Flow_07uhaa7 - - + + # Call Event <div><span>Hello {{my_var}}</span></div> Flow_0apfnjq - + Flow_07uhaa7 Flow_0apfnjq @@ -29,10 +29,10 @@ del(remove_this_var) - + - + diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/call_activity_end_event.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/call_activity_end_event.bpmn index d13377d12..e18a21ee6 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/call_activity_end_event.bpmn +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/call_activity_end_event.bpmn @@ -1,16 +1,16 @@ - + Flow_1g3dpd7 - - - + + + Flow_0ovgj6c Flow_0qdgvah - + # Main Workflow Hello {{my_other_var}} @@ -18,15 +18,15 @@ Hello {{my_other_var}} Flow_0izaz4f - - + + Flow_0qdgvah Flow_0izaz4f print(pre_var) print(my_var) print(my_other_var) - + Flow_1g3dpd7 Flow_0ovgj6c pre_var = 'some string' @@ -54,16 +54,16 @@ remove_this_var = 'something else' - + - + - + diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/data_object_gateway.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/data_object_gateway.bpmn new file mode 100644 index 000000000..dfba3be8d --- /dev/null +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/data_object_gateway.bpmn @@ -0,0 +1,91 @@ + + + + + Flow_0tx8esv + + + Flow_0tx8esv + Flow_0sg9gpx + + DataObjectReference_07x66g8 + + + + + Flow_0sg9gpx + Flow_1bc7w21 + Flow_16z4b2q + + + + Flow_1bc7w21 + + + val + + + Flow_16z4b2q + + + not val + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/invalid_process_top.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/invalid_process_top.bpmn deleted file mode 100644 index 28e738b5a..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/invalid_process_top.bpmn +++ /dev/null @@ -1,64 +0,0 @@ - - - - - Flow_1xegt6f - - - - - Flow_0qc6vpv - - - - - Flow_1xegt6f - Flow_11qyfqv - print('task1') - - - Flow_11qyfqv - Flow_0hntmrc - - - Flow_0hntmrc - Flow_0qc6vpv - print('task2') - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/proptest-Sub.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/proptest-Sub.bpmn deleted file mode 100644 index 84f987fa7..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/proptest-Sub.bpmn +++ /dev/null @@ -1,40 +0,0 @@ - - - - - Flow_0wro40z - - - - Flow_0061o90 - - - - Flow_0wro40z - Flow_0061o90 - valC=valB -valD=valA - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/proptest-Top.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/proptest-Top.bpmn deleted file mode 100644 index 323ace022..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/proptest-Top.bpmn +++ /dev/null @@ -1,111 +0,0 @@ - - - - - Flow_0du1rjv - - - Flow_0du1rjv - Flow_0gl3cli - valA = 1 - - - - Flow_0gl3cli - Flow_0qkplpb - - Flow_0i4qk1g - - - - Flow_001zehj - - - - Flow_0i4qk1g - Flow_001zehj - valB = valA - - - - - Flow_0qkplpb - Flow_1rptzfw - - - - - Flow_1u6sv80 - - - - Flow_1rptzfw - Flow_1u6sv80 - #print(valA) -#print(valB) -#print(valC) -#print(valD) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/reset_with_boundary_event.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/reset_with_boundary_event.bpmn new file mode 100644 index 000000000..1cc9fb616 --- /dev/null +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/reset_with_boundary_event.bpmn @@ -0,0 +1,125 @@ + + + + + Flow_03vnrmv + + + + Flow_1cztu3l + + + + Flow_18nptz1 + + + + Flow_1n2rgse + Flow_18nptz1 + + + + Flow_1n2rgse + + 'P14D' + + + + + Do you want to do the next steps? + Flow_03vnrmv + Flow_02q0uoy + + + FormC + Flow_02q0uoy + Flow_039y4lk + + Flow_1chdrkt + + + Flow_1chdrkt + Flow_1s4ala8 + + + + Flow_1s4ala8 + + + + + Flow_039y4lk + Flow_1cztu3l + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/same_id.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/same_id.bpmn deleted file mode 100644 index df911921d..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/same_id.bpmn +++ /dev/null @@ -1,61 +0,0 @@ - - - - - Flow_1fij5ow - - - Flow_1fij5ow - Flow_0gdswwp - - - - - Flow_0z5aj2a - - - - Flow_0gdswwp - Flow_0z5aj2a - - - This Task's id is "I_AM_TASK_1"  which is the same id of a task in workflow referenced in Task 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/same_id_sub.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/same_id_sub.bpmn deleted file mode 100644 index 74bc4147d..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/same_id_sub.bpmn +++ /dev/null @@ -1,49 +0,0 @@ - - - - - Flow_1fij5ow - - - Flow_1fij5ow - Flow_0gdswwp - - - - - Flow_0gdswwp - - - This Task's id is "I_AM_TASK_1"  which is the same id of a task in workflow referenced in Task 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/ActionManagementTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/ActionManagementTest.py index 1f1da73a3..c0a8de01b 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/ActionManagementTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/ActionManagementTest.py @@ -1,11 +1,9 @@ # -*- coding: utf-8 -*- - -import unittest import datetime import time from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from ..BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'matth' @@ -18,15 +16,16 @@ class ActionManagementTest(BpmnWorkflowTestCase): return (datetime.datetime.now() + datetime.timedelta(seconds=seconds)).isoformat() def setUp(self): - self.spec, self.subprocesses = self.load_workflow_spec('Test-Workflows/Action-Management.bpmn20.xml', 'Action Management') + self.spec, self.subprocesses = self.load_workflow_spec( + 'Test-Workflows/Action-Management.bpmn20.xml', + 'sid-efb89bb6-299a-4dc4-a50a-4286ec490604') self.workflow = BpmnWorkflow(self.spec, self.subprocesses) start_time = self.now_plus_seconds(self.START_TIME_DELTA) finish_time = self.now_plus_seconds(self.FINISH_TIME_DELTA) self.assertEqual(1, len(self.workflow.get_tasks(TaskState.READY))) - self.workflow.get_tasks(TaskState.READY)[0].set_data( - start_time=start_time, finish_time=finish_time) + self.workflow.get_tasks(TaskState.READY)[0].set_data(start_time=start_time, finish_time=finish_time) def testRunThroughHappy(self): self.do_next_exclusive_step("Review Action", choice='Approve') @@ -36,8 +35,7 @@ class ActionManagementTest(BpmnWorkflowTestCase): self.assertEqual(1, len(self.workflow.get_tasks(TaskState.READY))) self.assertEqual('NEW ACTION', self.workflow.get_tasks( TaskState.READY)[0].get_data('script_output')) - self.assertEqual('Cancel Action (if necessary)', - self.workflow.get_tasks(TaskState.READY)[0].task_spec.description) + self.assertEqual('Cancel Action (if necessary)', self.workflow.get_tasks(TaskState.READY)[0].task_spec.bpmn_name) time.sleep(self.START_TIME_DELTA) self.workflow.refresh_waiting_tasks() @@ -60,8 +58,7 @@ class ActionManagementTest(BpmnWorkflowTestCase): self.assertEqual(1, len(self.workflow.get_tasks(TaskState.WAITING))) self.assertEqual(1, len(self.workflow.get_tasks(TaskState.READY))) - self.assertEqual('Cancel Action (if necessary)', - self.workflow.get_tasks(TaskState.READY)[0].task_spec.description) + self.assertEqual('Cancel Action (if necessary)', self.workflow.get_tasks(TaskState.READY)[0].task_spec.bpmn_name) time.sleep(self.START_TIME_DELTA) self.workflow.refresh_waiting_tasks() @@ -73,22 +70,19 @@ class ActionManagementTest(BpmnWorkflowTestCase): self.workflow.do_engine_steps() self.assertEqual(2, len(self.workflow.get_tasks(TaskState.WAITING))) - self.assertEqual('Finish Time', self.workflow.get_tasks( - TaskState.WAITING)[1].task_spec.description) + self.assertEqual('Finish Time', self.workflow.get_tasks(TaskState.WAITING)[1].task_spec.bpmn_name) time.sleep(self.FINISH_TIME_DELTA) self.workflow.refresh_waiting_tasks() self.workflow.do_engine_steps() self.assertEqual(2, len(self.workflow.get_tasks(TaskState.WAITING))) - self.assertNotEqual( - 'Finish Time', self.workflow.get_tasks(TaskState.WAITING)[0].task_spec.description) + self.assertNotEqual('Finish Time', self.workflow.get_tasks(TaskState.WAITING)[0].task_spec.bpmn_name) overdue_escalation_task = [ - t for t in self.workflow.get_tasks() if t.task_spec.description == 'Overdue Escalation'] + t for t in self.workflow.get_tasks() if t.task_spec.bpmn_name == 'Overdue Escalation'] self.assertEqual(1, len(overdue_escalation_task)) overdue_escalation_task = overdue_escalation_task[0] self.assertEqual(TaskState.COMPLETED, overdue_escalation_task.state) - self.assertEqual( - 'ACTION OVERDUE', overdue_escalation_task.get_data('script_output')) + self.assertEqual('ACTION OVERDUE', overdue_escalation_task.get_data('script_output')) self.do_next_named_step("Complete Work", choice="Done") self.workflow.do_engine_steps() @@ -100,7 +94,6 @@ class ActionManagementTest(BpmnWorkflowTestCase): self.do_next_exclusive_step("Review Action", choice='Cancel') self.workflow.do_engine_steps() - self.assertTrue(self.workflow.is_completed()) def testRunThroughCancelAfterApproved(self): @@ -111,8 +104,7 @@ class ActionManagementTest(BpmnWorkflowTestCase): self.workflow.do_engine_steps() self.assertTrue(self.workflow.is_completed()) - self.assertEqual( - 'ACTION CANCELLED', self.workflow.get_data('script_output')) + self.assertEqual('ACTION CANCELLED', self.workflow.get_data('script_output')) def testRunThroughCancelAfterWorkStarted(self): self.do_next_exclusive_step("Review Action", choice='Approve') @@ -133,11 +125,4 @@ class ActionManagementTest(BpmnWorkflowTestCase): self.workflow.do_engine_steps() self.assertTrue(self.workflow.is_completed()) - self.assertEqual( - 'ACTION CANCELLED', self.workflow.get_data('script_output')) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(ActionManagementTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) + self.assertEqual('ACTION CANCELLED', self.workflow.get_data('script_output')) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/EventBasedGatewayTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/EventBasedGatewayTest.py index e1565d4fd..e5c990044 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/EventBasedGatewayTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/EventBasedGatewayTest.py @@ -3,7 +3,7 @@ from datetime import timedelta from SpiffWorkflow.bpmn.workflow import BpmnWorkflow from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine from SpiffWorkflow.bpmn.PythonScriptEngineEnvironment import TaskDataEnvironment -from SpiffWorkflow.bpmn.specs.events.event_definitions import MessageEventDefinition +from SpiffWorkflow.bpmn.specs.event_definitions import MessageEventDefinition from SpiffWorkflow.task import TaskState from ..BpmnWorkflowTestCase import BpmnWorkflowTestCase diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageInterruptsSpTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageInterruptsSpTest.py index 52d79dcc2..c36b43efe 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageInterruptsSpTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageInterruptsSpTest.py @@ -1,11 +1,9 @@ # -*- coding: utf-8 -*- -import unittest - from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from SpiffWorkflow.bpmn.specs.events.event_definitions import MessageEventDefinition -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from SpiffWorkflow.bpmn.specs.event_definitions import MessageEventDefinition +from ..BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'matth' @@ -13,7 +11,10 @@ __author__ = 'matth' class MessageInterruptsSpTest(BpmnWorkflowTestCase): def setUp(self): - self.spec, self.subprocesses = self.load_workflow_spec('Test-Workflows/*.bpmn20.xml', 'Message Interrupts SP', False) + self.spec, self.subprocesses = self.load_workflow_spec( + 'Test-Workflows/*.bpmn20.xml', + 'sid-607dfa9b-dbfd-41e8-94f8-42ae37f3b824', + False) def testRunThroughHappySaveAndRestore(self): @@ -59,9 +60,3 @@ class MessageInterruptsSpTest(BpmnWorkflowTestCase): self.workflow.do_engine_steps() self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(MessageInterruptsSpTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageInterruptsTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageInterruptsTest.py index 1ca3a201d..3fd35b00c 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageInterruptsTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageInterruptsTest.py @@ -4,7 +4,7 @@ import unittest from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from SpiffWorkflow.bpmn.specs.events.event_definitions import MessageEventDefinition +from SpiffWorkflow.bpmn.specs.event_definitions import MessageEventDefinition from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'matth' @@ -13,7 +13,10 @@ __author__ = 'matth' class MessageInterruptsTest(BpmnWorkflowTestCase): def setUp(self): - self.spec, self.subprocesses = self.load_workflow_spec('Test-Workflows/*.bpmn20.xml', 'Test Workflows', False) + self.spec, self.subprocesses = self.load_workflow_spec( + 'Test-Workflows/*.bpmn20.xml', + 'sid-b0903a88-fe74-4f93-b912-47b815ea8d1c', + False) def testRunThroughHappySaveAndRestore(self): diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageNonInterruptTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageNonInterruptTest.py index ce2d290e1..6674bf016 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageNonInterruptTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageNonInterruptTest.py @@ -1,11 +1,8 @@ # -*- coding: utf-8 -*- - -import unittest - from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from SpiffWorkflow.bpmn.specs.events.event_definitions import MessageEventDefinition -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from SpiffWorkflow.bpmn.specs.event_definitions import MessageEventDefinition +from ..BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'matth' @@ -13,7 +10,10 @@ __author__ = 'matth' class MessageNonInterruptTest(BpmnWorkflowTestCase): def setUp(self): - self.spec, self.subprocesses = self.load_workflow_spec('Test-Workflows/*.bpmn20.xml', 'Test Workflows', False) + self.spec, self.subprocesses = self.load_workflow_spec( + 'Test-Workflows/*.bpmn20.xml', + 'sid-b0903a88-fe74-4f93-b912-47b815ea8d1c', + False) def testRunThroughHappySaveAndRestore(self): @@ -179,9 +179,3 @@ class MessageNonInterruptTest(BpmnWorkflowTestCase): self.workflow.do_engine_steps() self.complete_subworkflow() self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(MessageNonInterruptTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageNonInterruptsSpTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageNonInterruptsSpTest.py index 2297654af..947a48440 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageNonInterruptsSpTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessageNonInterruptsSpTest.py @@ -1,11 +1,8 @@ # -*- coding: utf-8 -*- - -import unittest - from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from SpiffWorkflow.bpmn.specs.events.event_definitions import MessageEventDefinition -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from SpiffWorkflow.bpmn.specs.event_definitions import MessageEventDefinition +from ..BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'matth' @@ -13,7 +10,10 @@ __author__ = 'matth' class MessageNonInterruptsSpTest(BpmnWorkflowTestCase): def setUp(self): - self.spec, self.subprocesses = self.load_workflow_spec('Test-Workflows/*.bpmn20.xml', 'Message Non Interrupt SP', False) + self.spec, self.subprocesses = self.load_workflow_spec( + 'Test-Workflows/*.bpmn20.xml', + 'sid-b6b1212d-76ea-4ced-888b-a99fbbbca575', + False) def testRunThroughHappySaveAndRestore(self): @@ -123,9 +123,3 @@ class MessageNonInterruptsSpTest(BpmnWorkflowTestCase): self.workflow.do_engine_steps() self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(MessageNonInterruptsSpTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessagesTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessagesTest.py index 3757f1c7c..eb06ed188 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessagesTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MessagesTest.py @@ -1,11 +1,8 @@ # -*- coding: utf-8 -*- - -import unittest - from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from SpiffWorkflow.bpmn.specs.events.event_definitions import MessageEventDefinition -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from SpiffWorkflow.bpmn.specs.event_definitions import MessageEventDefinition +from ..BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'matth' @@ -13,7 +10,10 @@ __author__ = 'matth' class MessagesTest(BpmnWorkflowTestCase): def setUp(self): - self.spec, self.subprocesses = self.load_workflow_spec('Test-Workflows/*.bpmn20.xml', 'Test Workflows', False) + self.spec, self.subprocesses = self.load_workflow_spec( + 'Test-Workflows/*.bpmn20.xml', + 'sid-b0903a88-fe74-4f93-b912-47b815ea8d1c', + False) def testRunThroughHappy(self): @@ -27,7 +27,7 @@ class MessagesTest(BpmnWorkflowTestCase): self.workflow.catch(MessageEventDefinition('Test Message')) self.assertEqual(1, len(self.workflow.get_tasks(TaskState.READY))) - self.assertEqual('Test Message', self.workflow.get_tasks(TaskState.READY)[0].task_spec.description) + self.assertEqual('Test Message', self.workflow.get_tasks(TaskState.READY)[0].task_spec.bpmn_name) self.workflow.do_engine_steps() self.complete_subworkflow() @@ -53,9 +53,3 @@ class MessagesTest(BpmnWorkflowTestCase): self.workflow.do_engine_steps() self.complete_subworkflow() self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(MessagesTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MultipleCatchEventTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MultipleCatchEventTest.py index 791b7bd54..3d31aac91 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MultipleCatchEventTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MultipleCatchEventTest.py @@ -1,5 +1,5 @@ from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from SpiffWorkflow.bpmn.specs.events.event_definitions import MessageEventDefinition +from SpiffWorkflow.bpmn.specs.event_definitions import MessageEventDefinition from ..BpmnWorkflowTestCase import BpmnWorkflowTestCase diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MultipleEventsTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MultipleEventsTest.py index 2b6cf5df0..f0c8c1689 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MultipleEventsTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/MultipleEventsTest.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import unittest -from SpiffWorkflow.bpmn.specs.events.event_definitions import CancelEventDefinition, SignalEventDefinition +from SpiffWorkflow.bpmn.specs.event_definitions import CancelEventDefinition, SignalEventDefinition from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/TimeDurationParseTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/TimeDurationParseTest.py index cb4381a57..9371d5acf 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/TimeDurationParseTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/TimeDurationParseTest.py @@ -1,7 +1,7 @@ import unittest from datetime import datetime -from SpiffWorkflow.bpmn.specs.events.event_definitions import TimerEventDefinition +from SpiffWorkflow.bpmn.specs.event_definitions import TimerEventDefinition class TimeDurationParseTest(unittest.TestCase): "Non-exhaustive ISO durations, but hopefully covers basic support" diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/TimerIntermediateTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/TimerIntermediateTest.py index f1dce3c5c..9f1514576 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/TimerIntermediateTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/TimerIntermediateTest.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- -import unittest import datetime import time from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from ..BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'matth' @@ -13,7 +12,9 @@ __author__ = 'matth' class TimerIntermediateTest(BpmnWorkflowTestCase): def setUp(self): - self.spec, self.subprocesss = self.load_workflow_spec('Test-Workflows/Timer-Intermediate.bpmn20.xml', 'Timer Intermediate') + self.spec, self.subprocesss = self.load_workflow_spec( + 'Test-Workflows/Timer-Intermediate.bpmn20.xml', + 'sid-909dfba4-15dd-47b3-b7d4-88330891429a') self.workflow = BpmnWorkflow(self.spec, self.subprocesss) def testRunThroughHappy(self): @@ -35,11 +36,4 @@ class TimerIntermediateTest(BpmnWorkflowTestCase): self.assertEqual(1, len(self.workflow.get_tasks(TaskState.READY))) self.workflow.do_engine_steps() - self.assertEqual( - 0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(TimerIntermediateTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) + self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/TransactionSubprocssTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/TransactionSubprocssTest.py index 4abae178b..25c7d23a1 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/TransactionSubprocssTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/events/TransactionSubprocssTest.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - -import unittest from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase @@ -42,7 +39,6 @@ class TransactionSubprocessTest(BpmnWorkflowTestCase): error_none_task = self.workflow.get_tasks_from_spec_name("Catch_Error_None")[0] self.assertEqual(error_none_task.state, TaskState.CANCELLED) - def testSubworkflowCancelEvent(self): ready_tasks = self.workflow.get_tasks(TaskState.READY) @@ -124,10 +120,3 @@ class TransactionSubprocessTest(BpmnWorkflowTestCase): print_task = self.workflow.get_tasks_from_spec_name("Activity_Print_Data") self.assertEqual(len(print_task), 0) - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(TransactionSubprocessTest) - - -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/BaseParallelTestCase.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/BaseParallelTestCase.py similarity index 100% rename from SpiffWorkflow/tests/SpiffWorkflow/bpmn/BaseParallelTestCase.py rename to SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/BaseParallelTestCase.py diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelFromCamundaTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelGatewayTest.py similarity index 87% rename from SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelFromCamundaTest.py rename to SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelGatewayTest.py index 670c33d4d..b244cbd27 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelFromCamundaTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelGatewayTest.py @@ -1,17 +1,14 @@ # -*- coding: utf-8 -*- -import unittest from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from ..BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'matth' class ParallelFromCamunda(BpmnWorkflowTestCase): - # Should we move this to the Camunda package? Is this even testing anything Camunda related? - def setUp(self): spec, subprocesses = self.load_workflow_spec('Test-Workflows/Parallel.camunda.bpmn20.xml', 'Process_1hb021r') self.workflow = BpmnWorkflow(spec, subprocesses) @@ -63,9 +60,3 @@ class ParallelFromCamunda(BpmnWorkflowTestCase): self.assertEqual("taskA", self.workflow.last_task.data["taskA"]) self.assertEqual("taskB", self.workflow.last_task.data["taskB"]) self.assertEqual("taskC", self.workflow.last_task.data["taskC"]) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(ParallelFromCamunda) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelJoinLongInclusiveTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelJoinLongInclusiveTest.py similarity index 64% rename from SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelJoinLongInclusiveTest.py rename to SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelJoinLongInclusiveTest.py index 1105f473f..da82a1451 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelJoinLongInclusiveTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelJoinLongInclusiveTest.py @@ -1,16 +1,17 @@ # -*- coding: utf-8 -*- -import unittest from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from ..BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'matth' class ParallelJoinLongInclusiveTest(BpmnWorkflowTestCase): def setUp(self): - spec, subprocesses = self.load_workflow_spec('Test-Workflows/Parallel-Join-Long-Inclusive.bpmn20.xml', 'Parallel Join Long Inclusive') + spec, subprocesses = self.load_workflow_spec( + 'Test-Workflows/Parallel-Join-Long-Inclusive.bpmn20.xml', + 'sid-bae04828-c969-4480-8cfe-09ad1f97d81c') self.workflow = BpmnWorkflow(spec, subprocesses) self.workflow.do_engine_steps() @@ -18,8 +19,7 @@ class ParallelJoinLongInclusiveTest(BpmnWorkflowTestCase): self.assertEqual(2, len(self.workflow.get_tasks(TaskState.READY))) - self.do_next_named_step( - 'Thread 1 - Choose', choice='Yes', with_save_load=True) + self.do_next_named_step('Thread 1 - Choose', choice='Yes', with_save_load=True) self.workflow.do_engine_steps() for i in range(1, 13): self.do_next_named_step('Thread 1 - Task %d' % i) @@ -28,8 +28,7 @@ class ParallelJoinLongInclusiveTest(BpmnWorkflowTestCase): self.assertRaises(AssertionError, self.do_next_named_step, 'Done') self.assertEqual(1, len(self.workflow.get_tasks(TaskState.WAITING))) - self.do_next_named_step( - 'Thread 2 - Choose', choice='No', with_save_load=True) + self.do_next_named_step('Thread 2 - Choose', choice='No', with_save_load=True) self.workflow.do_engine_steps() self.do_next_named_step('Done', with_save_load=True) self.workflow.do_engine_steps() @@ -43,12 +42,10 @@ class ParallelJoinLongInclusiveTest(BpmnWorkflowTestCase): self.assertEqual(2, len(self.workflow.get_tasks(TaskState.READY))) - self.do_next_named_step( - 'Thread 2 - Choose', choice='No', with_save_load=True) + self.do_next_named_step('Thread 2 - Choose', choice='No', with_save_load=True) self.workflow.do_engine_steps() - self.do_next_named_step( - 'Thread 1 - Choose', choice='Yes', with_save_load=True) + self.do_next_named_step('Thread 1 - Choose', choice='Yes', with_save_load=True) self.workflow.do_engine_steps() for i in range(1, 13): self.do_next_named_step('Thread 1 - Task %d' % i) @@ -60,11 +57,4 @@ class ParallelJoinLongInclusiveTest(BpmnWorkflowTestCase): self.do_next_named_step('Thread 2 - No Task', with_save_load=True) self.workflow.do_engine_steps() - self.assertEqual( - 0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(ParallelJoinLongInclusiveTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) + self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelJoinLongTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelJoinLongTest.py similarity index 51% rename from SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelJoinLongTest.py rename to SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelJoinLongTest.py index 2d3989b6f..29772dd21 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelJoinLongTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelJoinLongTest.py @@ -1,16 +1,17 @@ # -*- coding: utf-8 -*- -import unittest from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from ..BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'matth' class ParallelJoinLongTest(BpmnWorkflowTestCase): def setUp(self): - spec, subprocesses = self.load_workflow_spec('Test-Workflows/Parallel-Join-Long.bpmn20.xml', 'Parallel Join Long') + spec, subprocesses = self.load_workflow_spec( + 'Test-Workflows/Parallel-Join-Long.bpmn20.xml', + 'sid-9274d78f-68da-4da6-a369-8a64feb3de52') self.workflow = BpmnWorkflow(spec, subprocesses) self.workflow.do_engine_steps() @@ -18,33 +19,27 @@ class ParallelJoinLongTest(BpmnWorkflowTestCase): self.assertEqual(2, len(self.workflow.get_tasks(TaskState.READY))) - self.do_next_named_step( - 'Thread 1 - Choose', choice='Yes', with_save_load=True) + self.do_next_named_step('Thread 1 - Choose', choice='Yes', with_save_load=True) self.workflow.do_engine_steps() - self.do_next_named_step( - 'Thread 2 - Choose', choice='Yes', with_save_load=True) + self.do_next_named_step('Thread 2 - Choose', choice='Yes', with_save_load=True) self.workflow.do_engine_steps() for i in range(1, 13): - self.do_next_named_step( - 'Thread 1 - Task %d' % i, with_save_load=True) + self.do_next_named_step('Thread 1 - Task %d' % i, with_save_load=True) self.workflow.do_engine_steps() - self.do_next_named_step( - 'Thread 2 - Task %d' % i, with_save_load=True) + self.do_next_named_step('Thread 2 - Task %d' % i, with_save_load=True) self.workflow.do_engine_steps() self.do_next_named_step('Done', with_save_load=True) self.workflow.do_engine_steps() - self.assertEqual( - 0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) + self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) def testRunThroughThread1First(self): self.assertEqual(2, len(self.workflow.get_tasks(TaskState.READY))) - self.do_next_named_step( - 'Thread 1 - Choose', choice='Yes', with_save_load=True) + self.do_next_named_step('Thread 1 - Choose', choice='Yes', with_save_load=True) self.workflow.do_engine_steps() for i in range(1, 13): self.do_next_named_step('Thread 1 - Task %d' % i) @@ -53,22 +48,13 @@ class ParallelJoinLongTest(BpmnWorkflowTestCase): self.assertRaises(AssertionError, self.do_next_named_step, 'Done') self.assertEqual(1, len(self.workflow.get_tasks(TaskState.WAITING))) - self.do_next_named_step( - 'Thread 2 - Choose', choice='Yes', with_save_load=True) + self.do_next_named_step('Thread 2 - Choose', choice='Yes', with_save_load=True) self.workflow.do_engine_steps() for i in range(1, 13): - self.do_next_named_step( - 'Thread 2 - Task %d' % i, with_save_load=True) + self.do_next_named_step('Thread 2 - Task %d' % i, with_save_load=True) self.workflow.do_engine_steps() self.do_next_named_step('Done', with_save_load=True) self.workflow.do_engine_steps() - self.assertEqual( - 0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(ParallelJoinLongTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) + self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelLoopingAfterJoinTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelLoopingAfterJoinTest.py similarity index 69% rename from SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelLoopingAfterJoinTest.py rename to SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelLoopingAfterJoinTest.py index 5a525e083..9772ed271 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelLoopingAfterJoinTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelLoopingAfterJoinTest.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- -import unittest from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BaseParallelTestCase import BaseParallelTestCase +from .BaseParallelTestCase import BaseParallelTestCase __author__ = 'matth' @@ -11,7 +10,7 @@ class ParallelLoopingAfterJoinTest(BaseParallelTestCase): def setUp(self): spec, subprocesses = self.load_workflow_spec( 'Test-Workflows/Parallel-Looping-After-Join.bpmn20.xml', - 'Parallel Looping After Join') + 'sid-41eb2b6c-08bc-4a61-b38b-5f32052139c5') self.workflow = BpmnWorkflow(spec, subprocesses) def test1(self): @@ -23,8 +22,3 @@ class ParallelLoopingAfterJoinTest(BaseParallelTestCase): ['Go', '1', '2', '2A', '2B', '2 Done', ('Retry?', 'Yes'), 'Go', '1', '2', '2A', '2B', '2 Done', ('Retry?', 'No'), 'Done'], save_restore=True) - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(ParallelLoopingAfterJoinTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelManyThreadsAtSamePointTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelManyThreadsAtSamePointTest.py similarity index 70% rename from SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelManyThreadsAtSamePointTest.py rename to SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelManyThreadsAtSamePointTest.py index 0bdbe75d2..944c1e902 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelManyThreadsAtSamePointTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelManyThreadsAtSamePointTest.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- -import unittest from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BaseParallelTestCase import BaseParallelTestCase +from .BaseParallelTestCase import BaseParallelTestCase __author__ = 'matth' @@ -11,7 +10,7 @@ class ParallelManyThreadsAtSamePointTest(BaseParallelTestCase): def setUp(self): spec, subprocesses = self.load_workflow_spec( 'Test-Workflows/Parallel-Many-Threads-At-Same-Point.bpmn20.xml', - 'Parallel Many Threads At Same Point') + 'sid-6d1186e0-fc1f-43d5-bdb4-c49df043944d') self.workflow = BpmnWorkflow(spec, subprocesses) def test1(self): @@ -22,12 +21,7 @@ class ParallelManyThreadsAtSamePointTest(BaseParallelTestCase): self._do_test(['1', 'Done', '2', 'Done', '3', 'Done', '4', 'Done'], only_one_instance=False, save_restore=True) - def test2(self): + def test3(self): self._do_test(['1', '2', 'Done', '3', '4', 'Done', 'Done', 'Done'], only_one_instance=False, save_restore=True) - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(ParallelManyThreadsAtSamePointTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelManyThreadsAtSamePointTestNested.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelManyThreadsAtSamePointTestNested.py similarity index 100% rename from SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelManyThreadsAtSamePointTestNested.py rename to SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelManyThreadsAtSamePointTestNested.py diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelMultiInstanceTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelMultiInstanceTest.py similarity index 99% rename from SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelMultiInstanceTest.py rename to SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelMultiInstanceTest.py index 314d73931..6ebc19553 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelMultiInstanceTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelMultiInstanceTest.py @@ -207,7 +207,7 @@ class ParallelMultiInstanceTaskTest(BpmnWorkflowTestCase): def check_reference(self, reference, name): self.assertIsInstance(reference, TaskDataReference) - self.assertEqual(reference.name, name) + self.assertEqual(reference.bpmn_id, name) def testParseInputOutput(self): spec, subprocess = self.load_workflow_spec('parallel_multiinstance_loop_input.bpmn', 'main') diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelMultipleSplitsAndJoinsTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelMultipleSplitsAndJoinsTest.py similarity index 73% rename from SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelMultipleSplitsAndJoinsTest.py rename to SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelMultipleSplitsAndJoinsTest.py index 7dc3484bd..52aafa412 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelMultipleSplitsAndJoinsTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelMultipleSplitsAndJoinsTest.py @@ -1,9 +1,7 @@ # -*- coding: utf-8 -*- -import unittest -from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BaseParallelTestCase import BaseParallelTestCase +from .BaseParallelTestCase import BaseParallelTestCase __author__ = 'matth' @@ -12,7 +10,7 @@ class ParallelMultipleSplitsAndJoinsTest(BaseParallelTestCase): def setUp(self): spec, subprocesses = self.load_workflow_spec( 'Test-Workflows/Parallel-Multiple-Splits-And-Joins.bpmn20.xml', - 'Parallel Multiple Splits And Joins') + 'sid-a90fa1f1-32a9-4a62-8ad4-8820a3fc6cc4') self.workflow = BpmnWorkflow(spec, subprocesses) def test1(self): @@ -31,8 +29,3 @@ class ParallelMultipleSplitsAndJoinsTest(BaseParallelTestCase): self._do_test( ['1', '1B', '1A', '1 Done', '!Done', '2', '2B', '2A', '2 Done', 'Done'], save_restore=True) - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(ParallelMultipleSplitsAndJoinsTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelMultipleSplitsTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelMultipleSplitsTest.py similarity index 70% rename from SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelMultipleSplitsTest.py rename to SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelMultipleSplitsTest.py index b813a1955..70a37f7f4 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelMultipleSplitsTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelMultipleSplitsTest.py @@ -1,16 +1,17 @@ # -*- coding: utf-8 -*- -import unittest from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from ..BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'matth' class ParallelMultipleSplitsTest(BpmnWorkflowTestCase): def setUp(self): - spec, subprocesses = self.load_workflow_spec('Test-Workflows/Parallel-Multiple-Splits.bpmn20.xml', 'Parallel Multiple Splits') + spec, subprocesses = self.load_workflow_spec( + 'Test-Workflows/Parallel-Multiple-Splits.bpmn20.xml', + 'sid-0f63def9-833d-4bcd-a6c4-8ef84a098b1a') self.workflow = BpmnWorkflow(spec, subprocesses) self.workflow.do_engine_steps() @@ -37,11 +38,4 @@ class ParallelMultipleSplitsTest(BpmnWorkflowTestCase): self.do_next_named_step('Done') self.workflow.do_engine_steps() - self.assertEqual( - 0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(ParallelMultipleSplitsTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) + self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelOnePathEndsTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelOnePathEndsTest.py similarity index 72% rename from SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelOnePathEndsTest.py rename to SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelOnePathEndsTest.py index 6583f7ce1..b76d70c99 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelOnePathEndsTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelOnePathEndsTest.py @@ -1,16 +1,17 @@ # -*- coding: utf-8 -*- -import unittest from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from ..BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'matth' class ParallelOnePathEndsTest(BpmnWorkflowTestCase): def setUp(self): - spec, subprocesses = self.load_workflow_spec('Test-Workflows/Parallel-One-Path-Ends.bpmn20.xml', 'Parallel One Path Ends') + spec, subprocesses = self.load_workflow_spec( + 'Test-Workflows/Parallel-One-Path-Ends.bpmn20.xml', + 'sid-33b2dda8-ca46-47ca-9f08-43de73abde9e') self.workflow = BpmnWorkflow(spec, subprocesses) self.workflow.do_engine_steps() @@ -27,8 +28,7 @@ class ParallelOnePathEndsTest(BpmnWorkflowTestCase): self.do_next_named_step('Done') self.workflow.do_engine_steps() - self.assertEqual( - 0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) + self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) def testRunThroughChoiceFirst(self): @@ -43,8 +43,7 @@ class ParallelOnePathEndsTest(BpmnWorkflowTestCase): self.do_next_named_step('Done') self.workflow.do_engine_steps() - self.assertEqual( - 0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) + self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) def testRunThroughParallelTaskFirstYes(self): @@ -62,11 +61,4 @@ class ParallelOnePathEndsTest(BpmnWorkflowTestCase): self.do_next_named_step('Done') self.workflow.do_engine_steps() - self.assertEqual( - 0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(ParallelOnePathEndsTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) + self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelOrderTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelOrderTest.py similarity index 80% rename from SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelOrderTest.py rename to SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelOrderTest.py index 3232ac6b0..467a9773d 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelOrderTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelOrderTest.py @@ -22,10 +22,10 @@ class ParallelOrderTest(BpmnWorkflowTestCase): self.assertFalse(self.workflow.is_completed()) self.assertEqual(4, len(self.workflow.get_ready_user_tasks())) tasks = self.workflow.get_ready_user_tasks() - self.assertEquals("Task 1", tasks[0].get_description()) - self.assertEquals("Task 2", tasks[1].get_description()) - self.assertEquals("Task 3", tasks[2].get_description()) - self.assertEquals("Task 4", tasks[3].get_description()) + self.assertEquals("Task 1", tasks[0].task_spec.bpmn_name) + self.assertEquals("Task 2", tasks[1].task_spec.bpmn_name) + self.assertEquals("Task 3", tasks[2].task_spec.bpmn_name) + self.assertEquals("Task 4", tasks[3].task_spec.bpmn_name) def suite(): diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelThenExclusiveTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelThenExclusiveTest.py similarity index 74% rename from SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelThenExclusiveTest.py rename to SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelThenExclusiveTest.py index bf1e7386b..cec72e882 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelThenExclusiveTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelThenExclusiveTest.py @@ -1,16 +1,17 @@ # -*- coding: utf-8 -*- -import unittest from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from ..BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'matth' class ParallelThenExclusiveTest(BpmnWorkflowTestCase): def setUp(self): - spec, subprocesses = self.load_workflow_spec('Test-Workflows/Parallel-Then-Exclusive.bpmn20.xml', 'Parallel Then Exclusive') + spec, subprocesses = self.load_workflow_spec( + 'Test-Workflows/Parallel-Then-Exclusive.bpmn20.xml', + 'sid-bb9ea2d5-58b6-43c7-8e77-6e28f71106f0') self.workflow = BpmnWorkflow(spec, subprocesses) self.workflow.do_engine_steps() @@ -29,8 +30,7 @@ class ParallelThenExclusiveTest(BpmnWorkflowTestCase): self.do_next_named_step('Done') self.workflow.do_engine_steps() - self.assertEqual( - 0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) + self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) def testRunThroughChoiceFirst(self): @@ -47,8 +47,7 @@ class ParallelThenExclusiveTest(BpmnWorkflowTestCase): self.do_next_named_step('Done') self.workflow.do_engine_steps() - self.assertEqual( - 0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) + self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) def testRunThroughChoiceThreadCompleteFirst(self): @@ -65,8 +64,7 @@ class ParallelThenExclusiveTest(BpmnWorkflowTestCase): self.do_next_named_step('Done') self.workflow.do_engine_steps() - self.assertEqual( - 0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) + self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) class ParallelThenExclusiveNoInclusiveTest(ParallelThenExclusiveTest): @@ -74,12 +72,8 @@ class ParallelThenExclusiveNoInclusiveTest(ParallelThenExclusiveTest): def setUp(self): spec, subprocesses = self.load_workflow_spec( 'Test-Workflows/Parallel-Then-Exclusive-No-Inclusive.bpmn20.xml', - 'Parallel Then Exclusive No Inclusive') + 'sid-900d26c9-beab-47a4-8092-4284bfb39927') self.workflow = BpmnWorkflow(spec, subprocesses) self.workflow.do_engine_steps() -def suite(): - return unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelThroughSameTaskTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelThroughSameTaskTest.py similarity index 82% rename from SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelThroughSameTaskTest.py rename to SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelThroughSameTaskTest.py index 07c790dc8..61a319fde 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/ParallelThroughSameTaskTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/ParallelThroughSameTaskTest.py @@ -1,16 +1,17 @@ # -*- coding: utf-8 -*- -import unittest from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +from ..BpmnWorkflowTestCase import BpmnWorkflowTestCase __author__ = 'matth' class ParallelThroughSameTaskTest(BpmnWorkflowTestCase): def setUp(self): - spec, subprocesses = self.load_workflow_spec('Test-Workflows/Parallel-Through-Same-Task.bpmn20.xml', 'Parallel Through Same Task') + spec, subprocesses = self.load_workflow_spec( + 'Test-Workflows/Parallel-Through-Same-Task.bpmn20.xml', + 'sid-57c563e3-fb68-4961-ae34-b6201e0c09e8') self.workflow = BpmnWorkflow(spec, subprocesses) self.workflow.do_engine_steps() @@ -35,8 +36,7 @@ class ParallelThroughSameTaskTest(BpmnWorkflowTestCase): self.do_next_named_step('Done') self.workflow.do_engine_steps() - self.assertEqual( - 0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) + self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) def testRepeatTasksReadyTogether(self): @@ -50,7 +50,7 @@ class ParallelThroughSameTaskTest(BpmnWorkflowTestCase): ready_tasks = self.workflow.get_tasks(TaskState.READY) self.assertEqual(2, len(ready_tasks)) self.assertEqual( - 'Repeated Task', ready_tasks[0].task_spec.description) + 'Repeated Task', ready_tasks[0].task_spec.bpmn_name) ready_tasks[0].run() self.workflow.do_engine_steps() # The inclusive gateway allows us through here, because there is no route for the other thread @@ -63,8 +63,7 @@ class ParallelThroughSameTaskTest(BpmnWorkflowTestCase): self.do_next_named_step('Done') self.workflow.do_engine_steps() - self.assertEqual( - 0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) + self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) def testRepeatTasksReadyTogetherSaveRestore(self): @@ -81,7 +80,7 @@ class ParallelThroughSameTaskTest(BpmnWorkflowTestCase): ready_tasks = self.workflow.get_tasks(TaskState.READY) self.assertEqual(2, len(ready_tasks)) self.assertEqual( - 'Repeated Task', ready_tasks[0].task_spec.description) + 'Repeated Task', ready_tasks[0].task_spec.bpmn_name) ready_tasks[0].run() self.workflow.do_engine_steps() self.save_restore() @@ -98,8 +97,7 @@ class ParallelThroughSameTaskTest(BpmnWorkflowTestCase): self.workflow.do_engine_steps() self.save_restore() - self.assertEqual( - 0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) + self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) def testNoRouteRepeatTaskFirst(self): @@ -124,8 +122,7 @@ class ParallelThroughSameTaskTest(BpmnWorkflowTestCase): self.workflow.do_engine_steps() self.save_restore() - self.assertEqual( - 0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) + self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) def testNoRouteNoTaskFirst(self): @@ -146,8 +143,7 @@ class ParallelThroughSameTaskTest(BpmnWorkflowTestCase): self.workflow.do_engine_steps() self.save_restore() - self.assertEqual( - 0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) + self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) def testNoRouteNoFirstThenRepeating(self): @@ -169,11 +165,4 @@ class ParallelThroughSameTaskTest(BpmnWorkflowTestCase): self.workflow.do_engine_steps() self.save_restore() - self.assertEqual( - 0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(ParallelThroughSameTaskTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) + self.assertEqual(0, len(self.workflow.get_tasks(TaskState.READY | TaskState.WAITING))) diff --git a/SpiffWorkflow/SpiffWorkflow/signavio/__init__.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/__init__.py similarity index 100% rename from SpiffWorkflow/SpiffWorkflow/signavio/__init__.py rename to SpiffWorkflow/tests/SpiffWorkflow/bpmn/parallel_gateway_tests/__init__.py diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/serializer/BpmnWorkflowSerializerTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/serializer/BpmnWorkflowSerializerTest.py index e2b92ab02..d03589534 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/serializer/BpmnWorkflowSerializerTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/serializer/BpmnWorkflowSerializerTest.py @@ -71,7 +71,10 @@ class BpmnWorkflowSerializerTest(BaseTestCase): try: self.assertRaises(TypeError, self.serializer.serialize_json, self.workflow) wf_spec_converter = BpmnWorkflowSerializer.configure_workflow_spec_converter() - custom_serializer = BpmnWorkflowSerializer(wf_spec_converter, version=self.SERIALIZER_VERSION,json_encoder_cls=MyJsonEncoder, json_decoder_cls=MyJsonDecoder) + custom_serializer = BpmnWorkflowSerializer(wf_spec_converter, + version=self.SERIALIZER_VERSION, + json_encoder_cls=MyJsonEncoder, + json_decoder_cls=MyJsonDecoder) serialized_workflow = custom_serializer.serialize_json(self.workflow) finally: a_task.data.pop('jsonTest',None) @@ -144,7 +147,7 @@ class BpmnWorkflowSerializerTest(BaseTestCase): ready_tasks = self.workflow.get_ready_user_tasks() ready_tasks[0].run() self.workflow.do_engine_steps() - results = self.serializer.serialize_json(self.workflow) + self.serializer.serialize_json(self.workflow) assert self.workflow.is_completed() assert 'y' in self.workflow.last_task.data assert 'x' not in self.workflow.last_task.data diff --git a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/serializer/VersionMigrationTest.py b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/serializer/VersionMigrationTest.py index 1b501b557..f3bdb8003 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/bpmn/serializer/VersionMigrationTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/bpmn/serializer/VersionMigrationTest.py @@ -20,7 +20,7 @@ class Version_1_0_Test(BaseTestCase): wf = self.serializer.deserialize_json(fh.read()) # We should be able to finish the workflow from this point ready_tasks = wf.get_tasks(TaskState.READY) - self.assertEqual('Action3', ready_tasks[0].task_spec.description) + self.assertEqual('Action3', ready_tasks[0].task_spec.bpmn_name) ready_tasks[0].run() wf.do_engine_steps() wf.refresh_waiting_tasks() @@ -66,7 +66,7 @@ class Version_1_1_Test(BaseTestCase): def test_check_multiinstance(self): fn = os.path.join(self.DATA_DIR, 'serialization', 'v1.1-multi.json') with self.assertRaises(VersionMigrationError) as ctx: - wf = self.serializer.deserialize_json(open(fn).read()) + self.serializer.deserialize_json(open(fn).read()) self.assertEqual(ctx.exception.message, "This workflow cannot be migrated because it contains MultiInstance Tasks") def test_remove_loop_reset(self): diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/BaseTestCase.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/BaseTestCase.py index 8cdde5629..85f6b969e 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/BaseTestCase.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/camunda/BaseTestCase.py @@ -1,16 +1,12 @@ # -*- coding: utf-8 -*- import os -from copy import deepcopy from SpiffWorkflow.bpmn.serializer.workflow import BpmnWorkflowSerializer from SpiffWorkflow.camunda.parser.CamundaParser import CamundaParser from SpiffWorkflow.camunda.serializer.config import CAMUNDA_SPEC_CONFIG -from SpiffWorkflow.dmn.serializer.task_spec import BusinessRuleTaskConverter - from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase -CAMUNDA_SPEC_CONFIG['task_specs'].append(BusinessRuleTaskConverter) __author__ = 'danfunk' diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/CallActivityMessageTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/CallActivityMessageTest.py index 8210b6c73..6942c301f 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/CallActivityMessageTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/camunda/CallActivityMessageTest.py @@ -42,7 +42,8 @@ class CallActivityMessageTest(BaseTestCase): current_task.run() self.workflow.do_engine_steps() self.complete_subworkflow() - if save_restore: self.save_restore() + if save_restore: + self.save_restore() ready_tasks = self.workflow.get_tasks(TaskState.READY) self.assertEqual(self.workflow.is_completed(),True,'Expected the workflow to be complete at this point') self.assertEqual(self.workflow.last_task.data,{'plan_details': 'Best', diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/CamundaParserTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/CamundaParserTest.py index 21d9ab02d..f52c34782 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/CamundaParserTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/camunda/CamundaParserTest.py @@ -1,8 +1,9 @@ from SpiffWorkflow.bpmn.parser.util import full_tag -from SpiffWorkflow.camunda.specs.UserTask import UserTask +from SpiffWorkflow.camunda.specs.user_task import UserTask +from SpiffWorkflow.camunda.specs.business_rule_task import BusinessRuleTask from SpiffWorkflow.camunda.parser.CamundaParser import CamundaParser from SpiffWorkflow.camunda.parser.task_spec import UserTaskParser, BusinessRuleTaskParser -from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask + from .BaseTestCase import BaseTestCase diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/ClashingNameTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/ClashingNameTest.py deleted file mode 100644 index a8d68cdcc..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/ClashingNameTest.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- coding: utf-8 -*- - - -import unittest - -from SpiffWorkflow.bpmn.workflow import BpmnWorkflow - -from tests.SpiffWorkflow.camunda.BaseTestCase import BaseTestCase - -__author__ = 'kellym' - -class ClashingNameTest(BaseTestCase): - """The example bpmn diagram tests both a set cardinality from user input - as well as looping over an existing array.""" - - def setUp(self): - spec, subprocesses = self.load_workflow_spec('token_trial_camunda_clash.bpmn', 'token') - self.workflow = BpmnWorkflow(spec, subprocesses) - - def testRunThroughHappy(self): - self.actual_test(save_restore=False) - - def testRunThroughSaveRestore(self): - self.actual_test(save_restore=True) - - def testRunThroughHappyReset(self): - self.actual_test(save_restore=False,reset_data=True,expected={'do_step':False,'C':'c'}) - - def testRunThroughSaveRestoreReset(self): - self.actual_test(save_restore=True,reset_data=True,expected={'do_step':False,'C':'c'}) - - def actual_test(self, save_restore=False, reset_data=False, expected=None): - - if expected is None: - expected = {'do_step': False, 'A': 'a', 'B': 'b', 'C': 'c'} - - self.workflow.do_engine_steps() - firsttaskid = None - steps = [{'taskname':'First', - 'formvar': 'do_step', - 'answer': True}, - {'taskname': 'FormA', - 'formvar': 'A', - 'answer': 'a'}, - {'taskname': 'FormB', - 'formvar': 'B', - 'answer': 'b'}, - ] - for step in steps: - task = self.workflow.get_ready_user_tasks()[0] - if firsttaskid == None: - firsttaskid = task.id - self.assertEqual(step['taskname'], task.task_spec.name) - task.update_data({step['formvar']: step['answer']}) - self.workflow.run_task_from_id(task.id) - self.workflow.do_engine_steps() - if save_restore: self.save_restore() - - self.workflow.reset_task_from_id(firsttaskid) - steps = [{'taskname':'First', - 'formvar': 'do_step', - 'answer': False}, - {'taskname': 'FormC', - 'formvar': 'C', - 'answer': 'c'}, - ] - for step in steps: - task = self.workflow.get_ready_user_tasks()[0] - self.assertEqual(step['taskname'], task.task_spec.name) - task.update_data({step['formvar']: step['answer']}) - self.workflow.run_task_from_id(task.id) - self.workflow.do_engine_steps() - if save_restore: self.save_restore() - - self.assertTrue(self.workflow.is_completed()) - - self.assertEqual({'do_step':False,'A':'a','B':'b','C':'c'}, - self.workflow.last_task.data) - - - - - - - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(ClashingNameTest) - -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/DMNCustomScriptTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/DMNCustomScriptTest.py deleted file mode 100644 index 21f03f756..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/DMNCustomScriptTest.py +++ /dev/null @@ -1,57 +0,0 @@ -import unittest -from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine -from SpiffWorkflow.bpmn.PythonScriptEngineEnvironment import TaskDataEnvironment -from SpiffWorkflow.bpmn.workflow import BpmnWorkflow - -from .BaseTestCase import BaseTestCase - - -def my_custom_function(txt): - return str(txt).upper() - - -class CustomScriptEngine(PythonScriptEngine): - - def __init__(self): - environment = TaskDataEnvironment({'my_custom_function': my_custom_function}) - super().__init__(environment=environment) - - -class DMNCustomScriptTest(BaseTestCase): - - def setUp(self): - - self.spec, subprocesses = self.load_workflow_spec('CustomScript.bpmn', 'start', 'CustomScript.dmn') - self.workflow = BpmnWorkflow(self.spec, script_engine=CustomScriptEngine()) - - def testConstructor(self): - pass # this is accomplished through setup. - - def complete_manual_task(self): - manual_task = self.workflow.get_tasks_from_spec_name('manual_task')[0] - self.workflow.run_task_from_id(manual_task.id) - self.workflow.do_engine_steps() - - def testDmnHappy(self): - self.workflow.do_engine_steps() - self.complete_manual_task() - self.workflow.do_engine_steps() - self.assertDictEqual(self.workflow.last_task.data, - {'a': 'BILL', 'dmn_result': 'BILL'}) - - def testDmnSaveRestore(self): - self.save_restore() - self.workflow.script_engine = CustomScriptEngine() - self.workflow.do_engine_steps() - self.complete_manual_task() - self.workflow.do_engine_steps() - self.assertDictEqual(self.workflow.last_task.data, - {'a': 'BILL', 'dmn_result': 'BILL'}) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(DMNCustomScriptTest) - - -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/DMNDictTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/DMNDictTest.py deleted file mode 100644 index 38cf09bac..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/DMNDictTest.py +++ /dev/null @@ -1,41 +0,0 @@ - -import unittest - -from SpiffWorkflow.bpmn.workflow import BpmnWorkflow - -from .BaseTestCase import BaseTestCase - -class DMNDictTest(BaseTestCase): - - def setUp(self): - self.spec, subprocesses = self.load_workflow_spec('dmndict.bpmn', 'start', 'dmndict.dmn') - self.workflow = BpmnWorkflow(self.spec) - self.expectedResult = {'inputvar': 1, 'pi': {'test': {'me': 'yup it worked'}, 'test2': {'other': 'yes'}}} - - def testDmnHappy(self): - self.workflow = BpmnWorkflow(self.spec) - self.workflow.do_engine_steps() - x = self.workflow.get_ready_user_tasks() - self.workflow.run_task_from_id(x[0].id) - self.workflow.do_engine_steps() - self.assertDictEqual(self.workflow.last_task.data, self.expectedResult) - - def testDmnSaveRestore(self): - self.workflow = BpmnWorkflow(self.spec) - self.workflow.do_engine_steps() - self.save_restore() - x = self.workflow.get_ready_user_tasks() - self.workflow.run_task_from_id(x[0].id) - self.workflow.do_engine_steps() - self.save_restore() - self.assertDictEqual(self.workflow.last_task.data, self.expectedResult) - - - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(DMNDictTest) - - -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/ExternalMessageBoundaryEventTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/ExternalMessageBoundaryEventTest.py index a416924af..02d5d4a83 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/ExternalMessageBoundaryEventTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/camunda/ExternalMessageBoundaryEventTest.py @@ -1,10 +1,7 @@ # -*- coding: utf-8 -*- - -import unittest - from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from SpiffWorkflow.camunda.specs.events.event_definitions import MessageEventDefinition +from SpiffWorkflow.camunda.specs.event_definitions import MessageEventDefinition from .BaseTestCase import BaseTestCase __author__ = 'kellym' @@ -13,7 +10,7 @@ __author__ = 'kellym' class ExternalMessageBoundaryTest(BaseTestCase): def setUp(self): - spec, subprocesses = self.load_workflow_spec('external_message.bpmn', 'ExternalMessage') + spec, subprocesses = self.load_workflow_spec('external_message.bpmn', 'Process_1iggtmi') self.workflow = BpmnWorkflow(spec, subprocesses) def testRunThroughHappy(self): @@ -36,10 +33,10 @@ class ExternalMessageBoundaryTest(BaseTestCase): # here because the thread just dies and doesn't lead to a task, we expect the data # to die with it. # item 1 should be at 'Pause' - self.assertEqual('Pause',ready_tasks[1].task_spec.description) + self.assertEqual('Pause',ready_tasks[1].task_spec.bpmn_name) self.assertEqual('SomethingImportant', ready_tasks[1].data['interrupt_var']) self.assertEqual(True, ready_tasks[1].data['caughtinterrupt']) - self.assertEqual('Meaningless User Task',ready_tasks[0].task_spec.description) + self.assertEqual('Meaningless User Task',ready_tasks[0].task_spec.bpmn_name) self.assertEqual(False, ready_tasks[0].data['caughtinterrupt']) ready_tasks[1].run() self.workflow.do_engine_steps() @@ -55,8 +52,3 @@ class ExternalMessageBoundaryTest(BaseTestCase): event.run() self.assertEqual('SomethingDrastic', event.data['reset_var']) self.assertEqual(False, event.data['caughtinterrupt']) - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(ExternalMessageBoundaryTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/FeelBusinessRuleTaskParserTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/FeelBusinessRuleTaskParserTest.py deleted file mode 100644 index 36a4a45ed..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/FeelBusinessRuleTaskParserTest.py +++ /dev/null @@ -1,43 +0,0 @@ -import unittest - -from SpiffWorkflow.task import TaskState - -from SpiffWorkflow.bpmn.workflow import BpmnWorkflow - -from .BaseTestCase import BaseTestCase - -class FeelBusinessRuleTaskParserTest(BaseTestCase): - - def setUp(self): - self.spec, subprocesses = self.load_workflow_spec( - 'ExclusiveGatewayIfElseAndDecision.bpmn', 'Process_1', 'test_integer_decision_feel.dmn') - self.workflow = BpmnWorkflow(self.spec) - - def testConstructor(self): - pass # this is accomplished through setup. - - def testDmnHappy(self): - self.workflow = BpmnWorkflow(self.spec) - self.workflow.get_tasks(TaskState.READY)[0].set_data(x=3) - self.workflow.do_engine_steps() - self.assertDictEqual(self.workflow.data, {'x': 3, 'y': 'A'}) - self.assertDictEqual(self.workflow.last_task.data, {'x': 3, 'y': 'A'}) - - def testDmnSaveRestore(self): - self.workflow = BpmnWorkflow(self.spec) - self.workflow.get_tasks(TaskState.READY)[0].set_data(x=3) - self.save_restore() - self.workflow.do_engine_steps() - self.save_restore() - self.assertDictEqual(self.workflow.data, {'x': 3, 'y': 'A'}) - self.assertDictEqual(self.workflow.last_task.data, {'x': 3, 'y': 'A'}) - - - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(FeelBusinessRuleTaskParserTest) - - -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/InvalidBusinessRuleTaskParserTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/InvalidBusinessRuleTaskParserTest.py index a00ed8599..916b3d98b 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/InvalidBusinessRuleTaskParserTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/camunda/InvalidBusinessRuleTaskParserTest.py @@ -1,4 +1,3 @@ -import os import unittest from SpiffWorkflow.exceptions import SpiffWorkflowException, WorkflowException diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/NIMessageBoundaryTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/NIMessageBoundaryTest.py index 68d1da71c..ab1bdaac2 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/NIMessageBoundaryTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/camunda/NIMessageBoundaryTest.py @@ -1,11 +1,8 @@ # -*- coding: utf-8 -*- - -import unittest - from SpiffWorkflow.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from tests.SpiffWorkflow.camunda.BaseTestCase import BaseTestCase +from .BaseTestCase import BaseTestCase __author__ = 'kellym' @@ -15,7 +12,7 @@ class NIMessageBoundaryTest(BaseTestCase): Non-Interrupting Timer boundary test """ def setUp(self): - spec, subprocesses = self.load_workflow_spec('noninterrupting-MessageBoundary.bpmn', 'MessageBoundary') + spec, subprocesses = self.load_workflow_spec('noninterrupting-MessageBoundary.bpmn', 'Process_1kjyavs') self.workflow = BpmnWorkflow(spec, subprocesses) def testRunThroughHappy(self): @@ -40,7 +37,7 @@ class NIMessageBoundaryTest(BaseTestCase): ready_tasks = self.workflow.get_tasks(TaskState.READY) for task in ready_tasks: response = answers.get(task.task_spec.name,None) - self.assertEqual(response==None, + self.assertEqual(response is None, False, 'We got a ready task that we did not expect - %s'%( task.task_spec.name)) @@ -61,14 +58,15 @@ class NIMessageBoundaryTest(BaseTestCase): ready_tasks = self.workflow.get_tasks(TaskState.READY) for task in ready_tasks: response = answers.get(task.task_spec.name,None) - self.assertEqual(response==None, + self.assertEqual(response is None, False, 'We got a ready task that we did not expect - %s'%( task.task_spec.name)) task.data[response[0]] = response[1] self.workflow.run_task_from_id(task.id) self.workflow.do_engine_steps() - if save_restore: self.save_restore() + if save_restore: + self.save_restore() ready_tasks = self.workflow.get_tasks(TaskState.READY) self.assertEqual(len(ready_tasks),1) @@ -92,8 +90,3 @@ class NIMessageBoundaryTest(BaseTestCase): 'work_completed': 'Lots of Stuff', 'work_late_reason': 'covid-19'}) - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(NIMessageBoundaryTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/ParseMultiInstanceTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/ParseMultiInstanceTest.py index 37f170ebf..dc6bd32bb 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/ParseMultiInstanceTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/camunda/ParseMultiInstanceTest.py @@ -20,10 +20,10 @@ class ParseMultiInstanceTest(BaseTestCase): self.save_restore() task_spec = self.workflow.get_tasks_from_spec_name('any_task')[0].task_spec - self.assertEqual(task_spec.data_input.name, 'input_data') - self.assertEqual(task_spec.data_output.name, 'output_data') - self.assertEqual(task_spec.input_item.name, 'output_item') - self.assertEqual(task_spec.output_item.name, 'output_item') + self.assertEqual(task_spec.data_input.bpmn_id, 'input_data') + self.assertEqual(task_spec.data_output.bpmn_id, 'output_data') + self.assertEqual(task_spec.input_item.bpmn_id, 'output_item') + self.assertEqual(task_spec.output_item.bpmn_id, 'output_item') ready_tasks = self.workflow.get_ready_user_tasks() self.assertEqual(len(ready_tasks), 3) @@ -52,7 +52,7 @@ class ParseMultiInstanceTest(BaseTestCase): self.save_restore() self.assertEqual(task_spec.data_input, None) - self.assertEqual(task_spec.input_item.name, 'output_item') + self.assertEqual(task_spec.input_item.bpmn_id, 'output_item') ready_tasks = self.workflow.get_ready_user_tasks() self.assertEqual(len(ready_tasks), 3) @@ -75,10 +75,10 @@ class ParseMultiInstanceTest(BaseTestCase): self.save_restore() task_spec = self.workflow.get_tasks_from_spec_name('any_task')[0].task_spec - self.assertEqual(task_spec.data_input.name, 'input_data') - self.assertEqual(task_spec.data_output.name, 'input_data') - self.assertEqual(task_spec.input_item.name, 'input_item') - self.assertEqual(task_spec.output_item.name, 'input_item') + self.assertEqual(task_spec.data_input.bpmn_id, 'input_data') + self.assertEqual(task_spec.data_output.bpmn_id, 'input_data') + self.assertEqual(task_spec.input_item.bpmn_id, 'input_item') + self.assertEqual(task_spec.output_item.bpmn_id, 'input_item') ready_tasks = self.workflow.get_ready_user_tasks() self.assertEqual(len(ready_tasks), 3) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/ResetTokenNestedParallelTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/ResetTokenNestedParallelTest.py index d8c535cab..2beeebf35 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/ResetTokenNestedParallelTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/camunda/ResetTokenNestedParallelTest.py @@ -69,14 +69,15 @@ class ResetTokenTestNestedParallel(BaseTestCase): ] for step in steps: task = self.workflow.get_ready_user_tasks()[0] - if firsttaskid == None and step['taskname']=='FormB2': + if firsttaskid is None and step['taskname']=='FormB2': firsttaskid = task.id self.assertEqual(step['taskname'], task.task_spec.name) task.update_data({step['formvar']: step['answer']}) self.workflow.run_task_from_id(task.id) self.workflow.do_engine_steps() - if save_restore: self.save_restore() - self.workflow.reset_task_from_id(firsttaskid) + if save_restore: + self.save_restore() + self.workflow.reset_from_task_id(firsttaskid) self.workflow.do_engine_steps() #NB - this won't test random access steps = [{'taskname': 'FormB2', @@ -93,8 +94,9 @@ class ResetTokenTestNestedParallel(BaseTestCase): task.update_data({step['formvar']: step['answer']}) self.workflow.run_task_from_id(task.id) self.workflow.do_engine_steps() - if save_restore: self.save_restore() - notworking = self.workflow.get_ready_user_tasks() + if save_restore: + self.save_restore() + self.workflow.get_ready_user_tasks() self.assertTrue(self.workflow.is_completed()) self.assertEqual({'First': 'Yes', 'A1': 'xa1', @@ -144,15 +146,16 @@ class ResetTokenTestNestedParallel(BaseTestCase): ] for step in steps: task = self.workflow.get_ready_user_tasks()[0] - if firsttaskid == None and step['taskname']=='FormA2': + if firsttaskid is None and step['taskname']=='FormA2': firsttaskid = task.id self.assertEqual(step['taskname'], task.task_spec.name) task.update_data({step['formvar']: step['answer']}) self.workflow.run_task_from_id(task.id) self.workflow.do_engine_steps() - if save_restore: self.save_restore() + if save_restore: + self.save_restore() - self.workflow.reset_task_from_id(firsttaskid) + self.workflow.reset_from_task_id(firsttaskid) #NB - this won't test random access steps = [{'taskname': 'FormA2', 'formvar': 'A2', @@ -184,7 +187,8 @@ class ResetTokenTestNestedParallel(BaseTestCase): task.update_data({step['formvar']: step['answer']}) self.workflow.run_task_from_id(task.id) self.workflow.do_engine_steps() - if save_restore: self.save_restore() + if save_restore: + self.save_restore() self.assertTrue(self.workflow.is_completed()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/ResetTokenParallelMatrixTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/ResetTokenParallelMatrixTest.py index 7b90e7f86..8751c13b8 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/ResetTokenParallelMatrixTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/camunda/ResetTokenParallelMatrixTest.py @@ -35,7 +35,7 @@ class ResetTokenTestParallelMatrix(BaseTestCase): Reset somewhere in the middle. It should complete the row that we Reset to, and retain all previous answers. """ - + self.workflow.do_engine_steps() firsttaskid = None steps = [{'taskname':'First', @@ -72,15 +72,16 @@ class ResetTokenTestParallelMatrix(BaseTestCase): ] for step in steps: task = self.workflow.get_ready_user_tasks()[0] - if firsttaskid == None and step['taskname']=='FormB2': + if firsttaskid is None and step['taskname']=='FormB2': firsttaskid = task.id self.assertEqual(step['taskname'], task.task_spec.name) task.update_data({step['formvar']: step['answer']}) self.workflow.run_task_from_id(task.id) self.workflow.do_engine_steps() - if save_restore: self.save_restore() + if save_restore: + self.save_restore() - self.workflow.reset_task_from_id(firsttaskid) + self.workflow.reset_from_task_id(firsttaskid) #NB - this won't test random access steps = [{'taskname': 'FormB2', 'formvar': 'B2', @@ -98,7 +99,8 @@ class ResetTokenTestParallelMatrix(BaseTestCase): task.update_data({step['formvar']: step['answer']}) self.workflow.run_task_from_id(task.id) self.workflow.do_engine_steps() - if save_restore: self.save_restore() + if save_restore: + self.save_restore() self.assertTrue(self.workflow.is_completed()) @@ -150,15 +152,16 @@ class ResetTokenTestParallelMatrix(BaseTestCase): ] for step in steps: task = self.workflow.get_ready_user_tasks()[0] - if firsttaskid == None and step['taskname']=='FormA2': + if firsttaskid is None and step['taskname']=='FormA2': firsttaskid = task.id self.assertEqual(step['taskname'], task.task_spec.name) task.update_data({step['formvar']: step['answer']}) self.workflow.run_task_from_id(task.id) self.workflow.do_engine_steps() - if save_restore: self.save_restore() + if save_restore: + self.save_restore() - self.workflow.reset_task_from_id(firsttaskid) + self.workflow.reset_from_task_id(firsttaskid) #NB - this won't test random access steps = [{'taskname': 'FormA2', 'formvar': 'A2', @@ -192,7 +195,8 @@ class ResetTokenTestParallelMatrix(BaseTestCase): task.update_data({step['formvar']: step['answer']}) self.workflow.run_task_from_id(task.id) self.workflow.do_engine_steps() - if save_restore: self.save_restore() + if save_restore: + self.save_restore() self.assertTrue(self.workflow.is_completed()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/ResetTokenParallelTaskCountTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/ResetTokenParallelTaskCountTest.py index bce088cb0..c2a643bc9 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/ResetTokenParallelTaskCountTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/camunda/ResetTokenParallelTaskCountTest.py @@ -41,7 +41,7 @@ class ResetTokenParallelTaskCountTest(BaseTestCase): # Reset the token to the first user task. # We should still have the same number of tasks. - task.reset_token({}, reset_data=True) + task.reset_token(data) self.assertEquals(total, len(self.workflow.get_tasks())) self.assertEquals(1, len(self.workflow.get_ready_user_tasks())) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/ResetTokenSubWorkflowTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/ResetTokenSubWorkflowTest.py index e444374ec..3eb6ddf87 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/ResetTokenSubWorkflowTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/camunda/ResetTokenSubWorkflowTest.py @@ -28,7 +28,7 @@ class ResetTokenTestSubProcess(BaseTestCase): Reset somewhere in the middle. It should complete the row that we Reset to, and retain all previous answers. """ - + self.workflow.do_engine_steps() firsttaskid = None steps = [{'taskname':'First', @@ -47,7 +47,7 @@ class ResetTokenTestSubProcess(BaseTestCase): for step in steps: task = self.workflow.get_ready_user_tasks()[0] - if firsttaskid == None and step['taskname']=='FormA1': + if firsttaskid is None and step['taskname']=='FormA1': firsttaskid = task.id self.assertEqual(step['taskname'], task.task_spec.name) task.update_data({step['formvar']: step['answer']}) @@ -56,7 +56,7 @@ class ResetTokenTestSubProcess(BaseTestCase): if save_restore: self.save_restore() - self.workflow.reset_task_from_id(firsttaskid) + self.workflow.reset_from_task_id(firsttaskid) #NB - this won't test random access steps = [{'taskname': 'FormA1', 'formvar': 'A1', @@ -122,18 +122,19 @@ class ResetTokenTestSubProcess(BaseTestCase): 'formvar': 'B2', 'answer': 'xb2'}, ] - + for step in steps: task = self.workflow.get_ready_user_tasks()[0] - if firsttaskid == None and step['taskname']=='FormA2': + if firsttaskid is None and step['taskname']=='FormA2': firsttaskid = task.id self.assertEqual(step['taskname'], task.task_spec.name) task.update_data({step['formvar']: step['answer']}) self.workflow.run_task_from_id(task.id) self.workflow.do_engine_steps() - if save_restore: self.save_restore() + if save_restore: + self.save_restore() - self.workflow.reset_task_from_id(firsttaskid) + self.workflow.reset_from_task_id(firsttaskid) #NB - this won't test random access steps = [{'taskname': 'FormA2', 'formvar': 'A2', @@ -167,7 +168,8 @@ class ResetTokenTestSubProcess(BaseTestCase): task.update_data({step['formvar']: step['answer']}) self.workflow.run_task_from_id(task.id) self.workflow.do_engine_steps() - if save_restore: self.save_restore() + if save_restore: + self.save_restore() self.assertTrue(self.workflow.is_completed()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/ResetTokenTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/ResetTokenTest.py deleted file mode 100644 index 495e940e7..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/ResetTokenTest.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- - -import unittest - -from SpiffWorkflow.bpmn.workflow import BpmnWorkflow - -from tests.SpiffWorkflow.camunda.BaseTestCase import BaseTestCase - -__author__ = 'kellym' - - -class ResetTokenTest(BaseTestCase): - """The example bpmn diagram tests both a set cardinality from user input - as well as looping over an existing array.""" - - def setUp(self): - spec, subprocesses = self.load_workflow_spec('token_trial.bpmn', 'token') - self.workflow = BpmnWorkflow(spec, subprocesses) - - def testRunThroughHappy(self): - self.actual_test(save_restore=False) - - def testRunThroughSaveRestore(self): - self.actual_test(save_restore=True) - - def testRunThroughHappyReset(self): - self.actual_test(save_restore=False,reset_data=True,expected={'do_step':False,'C':'c'}) - - def testRunThroughSaveRestoreReset(self): - self.actual_test(save_restore=True,reset_data=True,expected={'do_step':False,'C':'c'}) - - - - def actual_test(self, save_restore=False, reset_data=False, expected=None): - - if expected is None: - expected = {'do_step': False, 'A': 'a', 'B': 'b', 'C': 'c'} - - self.workflow.do_engine_steps() - firsttaskid = None - steps = [{'taskname':'First', - 'formvar': 'do_step', - 'answer': True}, - {'taskname': 'FormA', - 'formvar': 'A', - 'answer': 'a'}, - {'taskname': 'FormB', - 'formvar': 'B', - 'answer': 'b'}, - ] - for step in steps: - task = self.workflow.get_ready_user_tasks()[0] - if firsttaskid == None: - firsttaskid = task.id - self.assertEqual(step['taskname'], task.task_spec.name) - task.update_data({step['formvar']: step['answer']}) - self.workflow.run_task_from_id(task.id) - self.workflow.do_engine_steps() - if save_restore: self.save_restore() - - self.workflow.reset_task_from_id(firsttaskid) - steps = [{'taskname':'First', - 'formvar': 'do_step', - 'answer': False}, - {'taskname': 'FormC', - 'formvar': 'C', - 'answer': 'c'}, - ] - for step in steps: - task = self.workflow.get_ready_user_tasks()[0] - self.assertEqual(step['taskname'], task.task_spec.name) - task.update_data({step['formvar']: step['answer']}) - self.workflow.run_task_from_id(task.id) - self.workflow.do_engine_steps() - if save_restore: self.save_restore() - - self.assertTrue(self.workflow.is_completed()) - - self.assertEqual({'do_step':False,'A':'a','B':'b','C':'c'}, - self.workflow.last_task.data) - - - - - - - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(ResetTokenTest) - -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/SubWorkflowTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/SubWorkflowTest.py index 930020ce6..2d1a73c64 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/SubWorkflowTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/camunda/SubWorkflowTest.py @@ -34,7 +34,8 @@ class SubWorkflowTest(BaseTestCase): task.run() self.workflow.do_engine_steps() self.complete_subworkflow() - if save_restore: self.save_restore() + if save_restore: + self.save_restore() self.assertEqual(self.workflow.last_task.data,{'FieldA': 'A', 'FieldA1': 'A1', diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/UserTaskParserTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/UserTaskParserTest.py index d69dcdcc6..263543fff 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/UserTaskParserTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/camunda/UserTaskParserTest.py @@ -1,11 +1,9 @@ import unittest -from SpiffWorkflow.camunda.parser.task_spec import UserTaskParser from tests.SpiffWorkflow.camunda.BaseTestCase import BaseTestCase class UserTaskParserTest(BaseTestCase): - CORRELATE = UserTaskParser def setUp(self): self.spec, subprocesses = self.load_workflow_spec('random_fact.bpmn', 'random_fact') diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/data/CustomScript.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/camunda/data/CustomScript.bpmn deleted file mode 100644 index 586ff2a58..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/data/CustomScript.bpmn +++ /dev/null @@ -1,63 +0,0 @@ - - - - - Flow_0k348ph - - - - Flow_0k348ph - Flow_03rcoxc - a = my_custom_function("bill") - - - Flow_02v0zk5 - Flow_0pvahf7 - - - - Flow_0pvahf7 - - - - - Flow_03rcoxc - Flow_02v0zk5 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/data/dmn/CustomScript.dmn b/SpiffWorkflow/tests/SpiffWorkflow/camunda/data/dmn/CustomScript.dmn deleted file mode 100644 index 514ea4071..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/data/dmn/CustomScript.dmn +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - my_custom_function('bill') - - - - - - 'BILL' - - - my_custom_function('bill') - - - - - my_custom_function('jane') - - - my_custom_function('jane') - - - - - - - - - - - - - - - - - - - - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/data/dmn/dmndict.dmn b/SpiffWorkflow/tests/SpiffWorkflow/camunda/data/dmn/dmndict.dmn deleted file mode 100644 index 2670ed14c..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/data/dmn/dmndict.dmn +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - inputvar - - - - - - - - - - - - 1 - - - - - - 'yup it worked' - - - "yes" - - - - - 2 - - - - - - 'didnt expect this' - - - "No" - - - - - - - - - - - - - - - - - - - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/data/dmndict.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/camunda/data/dmndict.bpmn deleted file mode 100644 index 4778f57b3..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/data/dmndict.bpmn +++ /dev/null @@ -1,64 +0,0 @@ - - - - - Flow_0k348ph - - - - Flow_132mhgo - Flow_03rcoxc - pi = {'test':{'me':'stupid var'}} -inputvar = 1 - - - Flow_03rcoxc - Flow_0pvahf7 - - - - Flow_0pvahf7 - - - - - Flow_0k348ph - Flow_132mhgo - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/data/token_trial.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/camunda/data/token_trial.bpmn deleted file mode 100644 index 488db02b4..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/data/token_trial.bpmn +++ /dev/null @@ -1,144 +0,0 @@ - - - - - Flow_03vnrmv - - - Flow_0g2wjhu - Flow_0ya87hl - Flow_1qgke9w - - - Do you want to do the next steps? - - - - - - Flow_03vnrmv - Flow_13qpm6f - - - - Flow_13qpm6f - Flow_04bpvfu - Flow_0g2wjhu - - - Yes - - - do_step == True - - - do_step== False - - - - - - FormA - - - - - - Flow_04bpvfu - Flow_0ahlz50 - - - FormB - - - - - - Flow_0ahlz50 - Flow_0ya87hl - - - FormC - - - - - - Flow_1qgke9w - Flow_039y4lk - - - - Flow_039y4lk - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/data/token_trial_camunda_clash.bpmn b/SpiffWorkflow/tests/SpiffWorkflow/camunda/data/token_trial_camunda_clash.bpmn deleted file mode 100644 index d7aad5de5..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/data/token_trial_camunda_clash.bpmn +++ /dev/null @@ -1,142 +0,0 @@ - - - - - Flow_03vnrmv - - - Flow_0g2wjhu - Flow_0ya87hl - Flow_1qgke9w - - - Do you want to do the next steps? - - - - - - Flow_03vnrmv - Flow_13qpm6f - - - - Flow_13qpm6f - Flow_04bpvfu - Flow_0g2wjhu - - - Yes - do_step == True - - - do_step== False - - - - - - FormA - - - - - - Flow_04bpvfu - Flow_0ahlz50 - - - FormB - - - - - - Flow_0ahlz50 - Flow_0ya87hl - - - FormC - - - - - - Flow_1qgke9w - Flow_039y4lk - - - - Flow_039y4lk - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/camunda/specs/UserTaskSpecTest.py b/SpiffWorkflow/tests/SpiffWorkflow/camunda/specs/UserTaskSpecTest.py index 74726f8c4..7c805f6e9 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/camunda/specs/UserTaskSpecTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/camunda/specs/UserTaskSpecTest.py @@ -1,13 +1,12 @@ import unittest -from SpiffWorkflow.camunda.specs.UserTask import FormField, UserTask, Form, EnumFormField +from SpiffWorkflow.camunda.specs.user_task import FormField, UserTask, Form, EnumFormField from SpiffWorkflow.camunda.serializer.task_spec import UserTaskConverter from SpiffWorkflow.bpmn.serializer.helpers.dictionary import DictionaryConverter from SpiffWorkflow.specs.WorkflowSpec import WorkflowSpec class UserTaskSpecTest(unittest.TestCase): - CORRELATE = UserTask def create_instance(self): if 'testtask' in self.wf_spec.task_specs: @@ -122,12 +121,4 @@ class UserTaskSpecTest(unittest.TestCase): self.assertEquals("rubble", form_field.get_validation("barney")) def testIsEngineTask(self): - self.assertFalse(self.user_spec.is_engine_task()) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(UserTaskSpecTest) - - -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) + self.assertTrue(self.user_spec.manual) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/core/specs/DeepMergeTest.py b/SpiffWorkflow/tests/SpiffWorkflow/core/DeepMergeTest.py similarity index 84% rename from SpiffWorkflow/tests/SpiffWorkflow/core/specs/DeepMergeTest.py rename to SpiffWorkflow/tests/SpiffWorkflow/core/DeepMergeTest.py index 3fa3944ef..aae753d3a 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/core/specs/DeepMergeTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/core/DeepMergeTest.py @@ -1,20 +1,10 @@ # -*- coding: utf-8 -*- - - -import os -import sys -import unittest - +from unittest import TestCase from SpiffWorkflow.util.deep_merge import DeepMerge -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) -from .TaskSpecTest import TaskSpecTest - - -class DeepMergeTest(TaskSpecTest): - CORRELATE = DeepMerge +class DeepMergeTest(TestCase): def testBasicMerge(self): """ @@ -67,7 +57,3 @@ class DeepMergeTest(TaskSpecTest): self.assertEqual({"foods": [{"fruit": {"apples": "tasty", "oranges": "also tasty"}}]}, c) -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(DeepMergeTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/core/PersistSmallWorkflowTest.py b/SpiffWorkflow/tests/SpiffWorkflow/core/PersistSmallWorkflowTest.py index 2b2b03c02..20cc7f590 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/core/PersistSmallWorkflowTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/core/PersistSmallWorkflowTest.py @@ -94,7 +94,7 @@ class PersistSmallWorkflowTest(unittest.TestCase): self.assertEqual( 1, len([t for t in new_workflow.get_tasks() if t.task_spec.name == 'Root'])) - def testDeserialization(self): + def testDeserialization2(self): """ Tests the that deserialized workflow can be completed. """ diff --git a/SpiffWorkflow/tests/SpiffWorkflow/core/TaskTest.py b/SpiffWorkflow/tests/SpiffWorkflow/core/TaskTest.py index 9f5e0d120..894e25530 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/core/TaskTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/core/TaskTest.py @@ -36,10 +36,10 @@ class TaskTest(unittest.TestCase): c1 = root._add_child(task2) c11 = c1._add_child(task3) c111 = c11._add_child(task4) - c1111 = Task(workflow, task5, c111) - c112 = Task(workflow, task6, c11) - c12 = Task(workflow, task7, c1) - c2 = Task(workflow, task8, root) + Task(workflow, task5, c111) + Task(workflow, task6, c11) + Task(workflow, task7, c1) + Task(workflow, task8, root) c3 = Task(workflow, task9, root) c3.state = TaskState.COMPLETED diff --git a/SpiffWorkflow/tests/SpiffWorkflow/core/WorkflowTest.py b/SpiffWorkflow/tests/SpiffWorkflow/core/WorkflowTest.py index 726598e96..8dff9b1ad 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/core/WorkflowTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/core/WorkflowTest.py @@ -19,7 +19,7 @@ class WorkflowTest(unittest.TestCase): def testConstructor(self): wf_spec = WorkflowSpec() wf_spec.start.connect(Cancel(wf_spec, 'name')) - workflow = Workflow(wf_spec) + Workflow(wf_spec) def testBeginWorkflowStepByStep(self): """ diff --git a/SpiffWorkflow/tests/SpiffWorkflow/core/docTest.py b/SpiffWorkflow/tests/SpiffWorkflow/core/docTest.py index 7d600eb3c..b16a5f071 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/core/docTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/core/docTest.py @@ -3,6 +3,7 @@ import sys import unittest import os + dirname = os.path.abspath(os.path.dirname(__file__)) sys.path.insert(0, os.path.join(dirname, '..', '..', '..')) doc_dir = os.path.join(dirname, '..', '..', '..', 'doc') @@ -29,11 +30,11 @@ class TutorialTest(object): class Tutorial1Test(TutorialTest, unittest.TestCase): - tutorial_dir = os.path.join(doc_dir, 'non-bpmn', 'tutorial') + tutorial_dir = os.path.join(doc_dir, 'core', 'tutorial') class Tutorial2Test(TutorialTest, unittest.TestCase): - tutorial_dir = os.path.join(doc_dir, 'non-bpmn', 'custom-tasks') + tutorial_dir = os.path.join(doc_dir, 'core', 'custom-tasks') def suite(): diff --git a/SpiffWorkflow/tests/SpiffWorkflow/core/pattern_base.py b/SpiffWorkflow/tests/SpiffWorkflow/core/pattern_base.py index 50cb6c783..16474254d 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/core/pattern_base.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/core/pattern_base.py @@ -93,7 +93,8 @@ class WorkflowPatternTestCase: def test_xml_serializer(self): - prepare_result = lambda item: etree.tostring(item, pretty_print=True) + def prepare_result(item): + return etree.tostring(item, pretty_print=True) before, after = self.serialize(self.spec, xml_serializer) self.assertEqual(prepare_result(before), prepare_result(after)) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/core/specs/CeleryTest.py b/SpiffWorkflow/tests/SpiffWorkflow/core/specs/CeleryTest.py deleted file mode 100644 index 5f4232d72..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/core/specs/CeleryTest.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- - -import unittest -import pickle - -from .TaskSpecTest import TaskSpecTest -from SpiffWorkflow.specs.Celery import Celery -from SpiffWorkflow.specs.WorkflowSpec import WorkflowSpec -from SpiffWorkflow.operators import Attrib -from SpiffWorkflow.serializer.dict import DictionarySerializer -from base64 import b64encode - - -class CeleryTest(TaskSpecTest): - CORRELATE = Celery - - def create_instance(self): - if 'testtask' in self.wf_spec.task_specs: - del self.wf_spec.task_specs['testtask'] - return Celery(self.wf_spec, - 'testtask', 'call.name', - call_args=[Attrib('the_attribute'), 1], - description='foo', - named_kw=[], - dict_kw={} - ) - - def testTryFire(self): - pass - - def testRetryFire(self): - pass - - def testSerializationWithoutKwargs(self): - new_wf_spec = WorkflowSpec() - serializer = DictionarySerializer() - nokw = Celery(self.wf_spec, 'testnokw', 'call.name', - call_args=[Attrib('the_attribute'), 1]) - data = nokw.serialize(serializer) - nokw2 = Celery.deserialize(serializer, new_wf_spec, data) - self.assertDictEqual(nokw.kwargs, nokw2.kwargs) - - kw = Celery(self.wf_spec, 'testkw', 'call.name', - call_args=[Attrib('the_attribute'), 1], - some_arg={"key": "value"}) - data = kw.serialize(serializer) - kw2 = Celery.deserialize(serializer, new_wf_spec, data) - self.assertDictEqual(kw.kwargs, kw2.kwargs) - - # Has kwargs, but they belong to TaskSpec - kw_defined = Celery(self.wf_spec, 'testkwdef', 'call.name', - call_args=[Attrib('the_attribute'), 1], - some_ref=Attrib('value'), - defines={"key": "value"}) - data = kw_defined.serialize(serializer) - kw_defined2 = Celery.deserialize(serializer, new_wf_spec, data) - self.assertIsInstance(kw_defined2.kwargs['some_ref'], Attrib) - - args = [b64encode(pickle.dumps(v)) - for v in [Attrib('the_attribute'), 'ip', 'dc455016e2e04a469c01a866f11c0854']] - - data = {'R': b64encode(pickle.dumps('1'))} - # Comes from live data. Bug not identified, but there we are... - data = {'inputs': ['Wait:1'], 'lookahead': 2, 'description': '', - 'outputs': [], - 'args': args, - 'manual': False, - 'data': data, - 'pre_assign': [], - 'call': 'call.x', - 'internal': False, - 'post_assign': [], - 'id': 8, - 'result_key': None, - 'defines': data, - 'class': 'SpiffWorkflow.specs.Celery.Celery', - 'name': 'RS1:1'} - Celery.deserialize(serializer, new_wf_spec, data) - - -def suite(): - try: - import celery - except ImportError: - print("WARNING: Celery not found, not all tests are running!") - return lambda x: None - else: - return unittest.TestLoader().loadTestsFromTestCase(CeleryTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/core/specs/ExecuteTest.py b/SpiffWorkflow/tests/SpiffWorkflow/core/specs/ExecuteTest.py index 7bb677d79..606a5a8ce 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/core/specs/ExecuteTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/core/specs/ExecuteTest.py @@ -9,7 +9,6 @@ from .TaskSpecTest import TaskSpecTest from ..util import run_workflow class ExecuteTest(TaskSpecTest): - CORRELATE = Execute def create_instance(self): if 'testtask' in self.wf_spec.task_specs: diff --git a/SpiffWorkflow/tests/SpiffWorkflow/core/specs/JoinTest.py b/SpiffWorkflow/tests/SpiffWorkflow/core/specs/JoinTest.py index 9aca17d65..1a20a8724 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/core/specs/JoinTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/core/specs/JoinTest.py @@ -1,13 +1,10 @@ # -*- coding: utf-8 -*- -import unittest - from SpiffWorkflow.specs.Join import Join from .TaskSpecTest import TaskSpecTest class JoinTest(TaskSpecTest): - CORRELATE = Join def create_instance(self): if 'testtask' in self.wf_spec.task_specs: @@ -16,9 +13,3 @@ class JoinTest(TaskSpecTest): return Join(self.wf_spec, 'testtask', description='foo') - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(JoinTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/core/specs/MergeTest.py b/SpiffWorkflow/tests/SpiffWorkflow/core/specs/MergeTest.py index 745e18b25..801735ef0 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/core/specs/MergeTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/core/specs/MergeTest.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -import unittest - from .JoinTest import JoinTest from SpiffWorkflow.specs.Merge import Merge from SpiffWorkflow.specs.WorkflowSpec import WorkflowSpec @@ -10,7 +8,6 @@ from SpiffWorkflow.workflow import Workflow class MergeTest(JoinTest): - CORRELATE = Merge def create_instance(self): if 'testtask' in self.wf_spec.task_specs: @@ -83,9 +80,3 @@ class MergeTest(JoinTest): self.assertIn('simple1', found) self.assertIn('simple2', found) self.assertIn('unmerged', found) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(MergeTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/core/specs/SubWorkflowTest.py b/SpiffWorkflow/tests/SpiffWorkflow/core/specs/SubWorkflowTest.py index 892fff216..2b8a9a3c5 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/core/specs/SubWorkflowTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/core/specs/SubWorkflowTest.py @@ -5,14 +5,12 @@ import os from lxml import etree from SpiffWorkflow.specs.WorkflowSpec import WorkflowSpec -from SpiffWorkflow.specs.SubWorkflow import SubWorkflow from SpiffWorkflow.serializer.prettyxml import XmlSerializer from SpiffWorkflow.task import TaskState from SpiffWorkflow.workflow import Workflow class TaskSpecTest(unittest.TestCase): - CORRELATE = SubWorkflow def testConstructor(self): pass # FIXME @@ -120,9 +118,3 @@ class TaskSpecTest(unittest.TestCase): self.do_next_unique_task('join') self.do_next_unique_task('last') self.do_next_unique_task('End') - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(TaskSpecTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/core/specs/TaskSpecTest.py b/SpiffWorkflow/tests/SpiffWorkflow/core/specs/TaskSpecTest.py index ea36f7586..c3f365ec3 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/core/specs/TaskSpecTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/core/specs/TaskSpecTest.py @@ -10,7 +10,6 @@ from SpiffWorkflow.serializer.dict import DictionarySerializer class TaskSpecTest(unittest.TestCase): - CORRELATE = TaskSpec def create_instance(self): if 'testtask' in self.wf_spec.task_specs: @@ -78,8 +77,7 @@ class TaskSpecTest(unittest.TestCase): serialized) before = spec.serialize(serializer) after = new_spec.serialize(serializer) - self.assertEqual(before, after, 'Before:\n%s\nAfter:\n%s\n' % (before, - after)) + self.assertEqual(before, after, 'Before:\n%s\nAfter:\n%s\n' % (before, after)) def testAncestors(self): T1 = Simple(self.wf_spec, 'T1') @@ -110,9 +108,3 @@ class TaskSpecTest(unittest.TestCase): self.assertEqual(T1.ancestors(), [self.wf_spec.start]) self.assertEqual(T2.ancestors(), [T1, self.wf_spec.start]) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(TaskSpecTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/core/specs/TransformTest.py b/SpiffWorkflow/tests/SpiffWorkflow/core/specs/TransformTest.py index 77d826714..25e61d426 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/core/specs/TransformTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/core/specs/TransformTest.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -import unittest from ..util import run_workflow from .TaskSpecTest import TaskSpecTest @@ -8,7 +7,6 @@ from SpiffWorkflow.specs.Simple import Simple class TransformTest(TaskSpecTest): - CORRELATE = Transform def create_instance(self): if 'testtask' in self.wf_spec.task_specs: @@ -42,9 +40,3 @@ class TransformTest(TaskSpecTest): self.assertEqual(first.data.get('foo'), 1) self.assertEqual(last.data.get('foo'), 2) self.assertEqual(last.data.get('copy'), 2) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(TransformTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/core/specs/WorkflowSpecTest.py b/SpiffWorkflow/tests/SpiffWorkflow/core/specs/WorkflowSpecTest.py index 8c619ad74..43cae128c 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/core/specs/WorkflowSpecTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/core/specs/WorkflowSpecTest.py @@ -20,7 +20,6 @@ data_file = 'data.pkl' class WorkflowSpecTest(unittest.TestCase): - CORRELATE = WorkflowSpec def setUp(self): self.wf_spec = WorkflowSpec() @@ -105,7 +104,3 @@ class WorkflowSpecTest(unittest.TestCase): def testGetTaskSpecFromId(self): pass -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(WorkflowSpecTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/core/util.py b/SpiffWorkflow/tests/SpiffWorkflow/core/util.py index c8f02a4f0..0cb545805 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/core/util.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/core/util.py @@ -95,7 +95,7 @@ def run_workflow(test, wf_spec, expected_path, expected_data, workflow=None): if workflow.is_completed(): break time.sleep(0.5) - except: + except Exception: workflow.task_tree.dump() raise diff --git a/SpiffWorkflow/tests/SpiffWorkflow/dmn/DecisionRunner.py b/SpiffWorkflow/tests/SpiffWorkflow/dmn/DecisionRunner.py index efb9d89bf..47bcfbda4 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/dmn/DecisionRunner.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/dmn/DecisionRunner.py @@ -16,7 +16,7 @@ class Workflow: class TaskSpec: def __init__(self): self.name = "MockTestSpec" - self.description = "Mock Test Spec" + self.bpmn_name = "Mock Test Spec" class Task: def __init__(self, script_engine, data): diff --git a/SpiffWorkflow/tests/SpiffWorkflow/dmn/Dmn20151101VersionTest.py b/SpiffWorkflow/tests/SpiffWorkflow/dmn/Dmn20151101VersionTest.py deleted file mode 100644 index 9a98ae2f9..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/dmn/Dmn20151101VersionTest.py +++ /dev/null @@ -1,24 +0,0 @@ -import os -import unittest - -from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase - -class DmnVersionTest(BpmnWorkflowTestCase): - PARSER_CLASS = BpmnDmnParser - - def setUp(self): - self.parser = BpmnDmnParser() - - def testLoad(self): - dmn = os.path.join(os.path.dirname(__file__), 'data', - 'dmn_version_20151101_test.dmn') - self.assertIsNone(self.parser.add_dmn_file(dmn)) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(DmnVersionTest) - - -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/dmn/Dmn20191111VersionTest.py b/SpiffWorkflow/tests/SpiffWorkflow/dmn/Dmn20191111VersionTest.py deleted file mode 100644 index 8ff40003b..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/dmn/Dmn20191111VersionTest.py +++ /dev/null @@ -1,24 +0,0 @@ -import os -import unittest - -from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase - -class DmnVersionTest(BpmnWorkflowTestCase): - PARSER_CLASS = BpmnDmnParser - - def setUp(self): - self.parser = BpmnDmnParser() - - def testLoad(self): - dmn = os.path.join(os.path.dirname(__file__), 'data', - 'dmn_version_20191111_test.dmn') - self.assertIsNone(self.parser.add_dmn_file(dmn)) - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(DmnVersionTest) - - -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/dmn/HitPolicyTest.py b/SpiffWorkflow/tests/SpiffWorkflow/dmn/HitPolicyTest.py index 898aeeb3a..47fcc1751 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/dmn/HitPolicyTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/dmn/HitPolicyTest.py @@ -2,14 +2,11 @@ import os import unittest from SpiffWorkflow.bpmn.serializer.helpers.dictionary import DictionaryConverter -from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser -from SpiffWorkflow.dmn.serializer.task_spec import BusinessRuleTaskConverter -from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase -from tests.SpiffWorkflow.dmn.python_engine.PythonDecisionRunner import PythonDecisionRunner +from SpiffWorkflow.camunda.serializer.task_spec import BusinessRuleTaskConverter +from .python_engine.PythonDecisionRunner import PythonDecisionRunner -class HitPolicyTest(BpmnWorkflowTestCase): - PARSER_CLASS = BpmnDmnParser +class HitPolicyTest(unittest.TestCase): def testHitPolicyUnique(self): file_name = os.path.join(os.path.dirname(__file__), 'data', 'unique_hit.dmn') @@ -35,13 +32,7 @@ class HitPolicyTest(BpmnWorkflowTestCase): runner = PythonDecisionRunner(file_name) decision_table = runner.decision_table self.assertEqual("COLLECT", decision_table.hit_policy) - dict = BusinessRuleTaskConverter(DictionaryConverter()).decision_table_to_dict(decision_table) - new_table = BusinessRuleTaskConverter(DictionaryConverter()).decision_table_from_dict(dict) + converter = BusinessRuleTaskConverter(DictionaryConverter()) + dict = converter.decision_table_to_dict(decision_table) + new_table = converter.decision_table_from_dict(dict) self.assertEqual("COLLECT", new_table.hit_policy) - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(HitPolicyTest) - - -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/dmn/ParserTest.py b/SpiffWorkflow/tests/SpiffWorkflow/dmn/ParserTest.py new file mode 100644 index 000000000..52739c723 --- /dev/null +++ b/SpiffWorkflow/tests/SpiffWorkflow/dmn/ParserTest.py @@ -0,0 +1,12 @@ +import os +import unittest + +from .python_engine.PythonDecisionRunner import PythonDecisionRunner + +class ParserTest(unittest.TestCase): + + def test_input_dash(self): + filename = os.path.join(os.path.dirname(__file__) , 'data', 'input_dash.dmn') + runner = PythonDecisionRunner(filename) + result = runner.result({'a': ''}) + self.assertDictEqual(result, {'b': 'anything goes'}) \ No newline at end of file diff --git a/SpiffWorkflow/tests/SpiffWorkflow/dmn/DmnFailVersionTest.py b/SpiffWorkflow/tests/SpiffWorkflow/dmn/VersionTest.py similarity index 54% rename from SpiffWorkflow/tests/SpiffWorkflow/dmn/DmnFailVersionTest.py rename to SpiffWorkflow/tests/SpiffWorkflow/dmn/VersionTest.py index a645aa93d..22d26d597 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/dmn/DmnFailVersionTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/dmn/VersionTest.py @@ -3,41 +3,42 @@ import os from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser +data_dir = os.path.join(os.path.dirname(__file__), 'data', 'dmn_version_test') class DmnVersionTest(unittest.TestCase): def setUp(self): self.parser = BpmnDmnParser() - def testLoadV1_2_supported(self): + def test_load_v1_0(self): + filename = os.path.join(data_dir, 'dmn_version_20151101_test.dmn') + self.parser.add_dmn_file(filename) + + def test_load_v1_1(self): + filename = os.path.join(data_dir, 'dmn_version_20191111_test.dmn') + self.parser.add_dmn_file(filename) + + def test_load_v1_2_supported(self): self._assert_parse_all_pass('v1_2_supported') - def testLoadV1_2_unsupported(self): + def test_load_v1_2_unsupported(self): self._assert_parse_all_fail('v1_2_unsupported') - def testLoadV1_3_supported(self): + def test_load_v1_3_supported(self): self._assert_parse_all_pass('v1_3_supported') - def testLoadV1_3_unsupported(self): + def test_load_v1_3_unsupported(self): self._assert_parse_all_fail('v1_3_unsupported') def _assert_parse_all_pass(self, dir_path): - dirname = os.path.join(os.path.dirname(__file__), 'data', 'dmn_version_test', dir_path) + dirname = os.path.join(data_dir, dir_path) self.parser.add_dmn_files_by_glob(f'{dirname}/*.dmn') for parser in self.parser.dmn_parsers.values(): parser.parse() - self.assertIsNotNone(parser.get_id()) + self.assertIsNotNone(parser.bpmn_id) self.assertIsNotNone(parser.get_name()) def _assert_parse_all_fail(self, dir_path): - dirname = os.path.join(os.path.dirname(__file__), 'data', 'dmn_version_test', dir_path) + dirname = os.path.join(data_dir, dir_path) with self.assertRaises(IndexError): self.parser.add_dmn_files_by_glob(f'{dirname}/*.dmn') - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(DmnVersionTest) - - -if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/dmn/data/dmn_version_fail_test.dmn b/SpiffWorkflow/tests/SpiffWorkflow/dmn/data/dmn_version_fail_test.dmn deleted file mode 100644 index 6bcee64f5..000000000 --- a/SpiffWorkflow/tests/SpiffWorkflow/dmn/data/dmn_version_fail_test.dmn +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - They are allergic to peanuts - - mGender Description - "PEANUTS" in ? - - - "isPeanuts" - - - - They are not allergic to peanuts - - "PEANUTS" not in ? - - - "IsNotPeanuts" - - - - - diff --git a/SpiffWorkflow/tests/SpiffWorkflow/dmn/data/dmn_version_20151101_test.dmn b/SpiffWorkflow/tests/SpiffWorkflow/dmn/data/dmn_version_test/dmn_version_20151101_test.dmn similarity index 100% rename from SpiffWorkflow/tests/SpiffWorkflow/dmn/data/dmn_version_20151101_test.dmn rename to SpiffWorkflow/tests/SpiffWorkflow/dmn/data/dmn_version_test/dmn_version_20151101_test.dmn diff --git a/SpiffWorkflow/tests/SpiffWorkflow/dmn/data/dmn_version_20191111_test.dmn b/SpiffWorkflow/tests/SpiffWorkflow/dmn/data/dmn_version_test/dmn_version_20191111_test.dmn similarity index 100% rename from SpiffWorkflow/tests/SpiffWorkflow/dmn/data/dmn_version_20191111_test.dmn rename to SpiffWorkflow/tests/SpiffWorkflow/dmn/data/dmn_version_test/dmn_version_20191111_test.dmn diff --git a/SpiffWorkflow/tests/SpiffWorkflow/dmn/data/input_dash.dmn b/SpiffWorkflow/tests/SpiffWorkflow/dmn/data/input_dash.dmn new file mode 100644 index 000000000..0e64c012f --- /dev/null +++ b/SpiffWorkflow/tests/SpiffWorkflow/dmn/data/input_dash.dmn @@ -0,0 +1,28 @@ + + + + + + + a + + + + + + - + + + "anything goes" + + + + + + + + + + + + diff --git a/SpiffWorkflow/tests/SpiffWorkflow/dmn/feel_engine/FeelListDecisionTest.py b/SpiffWorkflow/tests/SpiffWorkflow/dmn/feel_engine/FeelListDecisionTest.py index 5a21075a0..3fe22f706 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/dmn/feel_engine/FeelListDecisionTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/dmn/feel_engine/FeelListDecisionTest.py @@ -15,7 +15,7 @@ class FeelListDecisionTestClass(unittest.TestCase): res = self.runner.decide({'allergies':["PEANUTS", "SPAM"]}) self.assertEqual(res.description, 'They are allergic to peanuts') - def test_string_decision_string_output1(self): + def test_string_decision_string_output2(self): res = self.runner.decide({'allergies':["SPAM", "SPAM"]}) self.assertEqual(res.description, 'They are not allergic to peanuts') diff --git a/SpiffWorkflow/tests/SpiffWorkflow/dmn/python_engine/InvalidBusinessRuleNameErrorTest.py b/SpiffWorkflow/tests/SpiffWorkflow/dmn/python_engine/InvalidBusinessRuleNameErrorTest.py index adc474d40..95a278b9d 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/dmn/python_engine/InvalidBusinessRuleNameErrorTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/dmn/python_engine/InvalidBusinessRuleNameErrorTest.py @@ -8,7 +8,7 @@ class InvalidBusinessRuleNameErrorTest(unittest.TestCase): def test_integer_decision_string_output_inclusive(self): runner = PythonDecisionRunner('invalid_decision_name_error.dmn') try: - res = runner.decide({'spam': 1}) + runner.decide({'spam': 1}) except Exception as e: self.assertRegex(str(e), "Did you mean 'spam'") diff --git a/SpiffWorkflow/tests/SpiffWorkflow/dmn/python_engine/ListDecisionTest.py b/SpiffWorkflow/tests/SpiffWorkflow/dmn/python_engine/ListDecisionTest.py index c4aff2521..648bae499 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/dmn/python_engine/ListDecisionTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/dmn/python_engine/ListDecisionTest.py @@ -13,10 +13,10 @@ class ListDecisionTestClass(unittest.TestCase): cls.runner = PythonDecisionRunner('list_decision.dmn') def test_string_decision_string_output1(self): - res = self.runner.decide({'allergies',["PEANUTS", "SPAM"]}) + res = self.runner.decide({'allergies':["PEANUTS", "SPAM"]}) self.assertEqual(res.description, 'They are allergic to peanuts') - def test_string_decision_string_output1(self): + def test_string_decision_string_output2(self): res = self.runner.decide({'allergies':["SPAM", "SPAM"]}) self.assertEqual(res.description, 'They are not allergic to peanuts') diff --git a/SpiffWorkflow/tests/SpiffWorkflow/spiff/CorrelationTest.py b/SpiffWorkflow/tests/SpiffWorkflow/spiff/CorrelationTest.py index 014e8f8ed..80d26c968 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/spiff/CorrelationTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/spiff/CorrelationTest.py @@ -22,7 +22,7 @@ class CorrelationTest(BaseTestCase): for idx, task in enumerate(self.workflow.get_ready_user_tasks()): task.data['task_num'] = idx task.data['task_name'] = f'subprocess {idx}' - task.data['extra_data'] = f'unused data' + task.data['extra_data'] = 'unused data' task.run() self.workflow.do_engine_steps() ready_tasks = self.workflow.get_ready_user_tasks() @@ -47,11 +47,12 @@ class DualConversationTest(BaseTestCase): workflow.do_engine_steps() messages = workflow.get_bpmn_messages() self.assertEqual(len(messages), 2) - message_one = [ msg for msg in messages if msg.name== 'Message Send One' ][0] - message_two = [ msg for msg in messages if msg.name== 'Message Send Two' ][0] + [ msg for msg in messages if msg.name== 'Message Send One' ][0] + [ msg for msg in messages if msg.name== 'Message Send Two' ][0] # fixme: This seemed to test that we get a nested structure of correlation keys and correlation properties - # Perhaps there should be a way to get the keys and thier associated properties - but things should not default to a nested structure. + # Perhaps there should be a way to get the keys and thier associated properties - + # but things should not default to a nested structure. # self.assertIn('message_correlation_key_one', message_one.correlations) # self.assertNotIn('message_correlation_key_one', message_two.correlations) diff --git a/SpiffWorkflow/tests/SpiffWorkflow/spiff/PrescriptPostscriptTest.py b/SpiffWorkflow/tests/SpiffWorkflow/spiff/PrescriptPostscriptTest.py index c44f1749e..7f8ac82f2 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/spiff/PrescriptPostscriptTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/spiff/PrescriptPostscriptTest.py @@ -52,7 +52,7 @@ class PrescriptPostsciptTest(BaseTestCase): self.workflow = BpmnWorkflow(spec, subprocesses) if save_restore: self.save_restore() - ready_tasks = self.workflow.get_tasks(TaskState.READY) + self.workflow.get_tasks(TaskState.READY) # Calling do-engine steps without setting variables will raise an exception. with self.assertRaises(SpiffWorkflowException) as se: self.workflow.do_engine_steps() diff --git a/SpiffWorkflow/tests/SpiffWorkflow/spiff/ScriptUnitTestExtensionsTest.py b/SpiffWorkflow/tests/SpiffWorkflow/spiff/ScriptUnitTestExtensionsTest.py index 9e78fd602..b643f556e 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/spiff/ScriptUnitTestExtensionsTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/spiff/ScriptUnitTestExtensionsTest.py @@ -13,7 +13,8 @@ class ScriptUnitTestExtensionsTest(BaseTestCase): def task_test(self, save_restore=False): - spec, subprocesses = self.load_workflow_spec('script_task_with_unit_tests.bpmn', 'Process_ScriptTaskWithUnitTests') + spec, subprocesses = self.load_workflow_spec('script_task_with_unit_tests.bpmn', + 'Process_ScriptTaskWithUnitTests') self.workflow = BpmnWorkflow(spec, subprocesses) self.workflow.do_engine_steps() if save_restore: diff --git a/SpiffWorkflow/tests/SpiffWorkflow/spiff/ServiceTaskTest.py b/SpiffWorkflow/tests/SpiffWorkflow/spiff/ServiceTaskTest.py index 66b2d86a0..199746e55 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/spiff/ServiceTaskTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/spiff/ServiceTaskTest.py @@ -3,13 +3,13 @@ import json import os import sys import unittest +from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine +from SpiffWorkflow.bpmn.workflow import BpmnWorkflow +from .BaseTestCase import BaseTestCase dirname = os.path.dirname(__file__) sys.path.insert(0, os.path.join(dirname, '..', '..', '..')) -from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine -from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from .BaseTestCase import BaseTestCase class ServiceTaskDelegate: @staticmethod diff --git a/SpiffWorkflow/tests/SpiffWorkflow/spiff/ServiceTaskVariableTest.py b/SpiffWorkflow/tests/SpiffWorkflow/spiff/ServiceTaskVariableTest.py index 834f0c6f1..b739c167b 100644 --- a/SpiffWorkflow/tests/SpiffWorkflow/spiff/ServiceTaskVariableTest.py +++ b/SpiffWorkflow/tests/SpiffWorkflow/spiff/ServiceTaskVariableTest.py @@ -3,13 +3,13 @@ import json import os import sys import unittest +from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine +from SpiffWorkflow.bpmn.workflow import BpmnWorkflow +from .BaseTestCase import BaseTestCase dirname = os.path.dirname(__file__) sys.path.insert(0, os.path.join(dirname, '..', '..', '..')) -from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine -from SpiffWorkflow.bpmn.workflow import BpmnWorkflow -from .BaseTestCase import BaseTestCase class ServiceTaskDelegate: @staticmethod