mirror of
https://github.com/sartography/spiff-arena.git
synced 2025-02-23 22:58:09 +00:00
merged in main and resolved conflicts w/ burnettk
This commit is contained in:
commit
e2b8f17a7e
26
.github/workflows/backend_tests.yml
vendored
26
.github/workflows/backend_tests.yml
vendored
@ -16,10 +16,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
# FIXME: https://github.com/mysql/mysql-connector-python/pull/86
|
- { python: "3.11", os: "ubuntu-latest", session: "safety" }
|
||||||
# put back when poetry update protobuf mysql-connector-python updates protobuf
|
|
||||||
# right now mysql is forcing protobuf to version 3
|
|
||||||
# - { python: "3.11", os: "ubuntu-latest", session: "safety" }
|
|
||||||
- { python: "3.11", os: "ubuntu-latest", session: "mypy" }
|
- { python: "3.11", os: "ubuntu-latest", session: "mypy" }
|
||||||
- { python: "3.10", os: "ubuntu-latest", session: "mypy" }
|
- { python: "3.10", os: "ubuntu-latest", session: "mypy" }
|
||||||
- { python: "3.9", os: "ubuntu-latest", session: "mypy" }
|
- { python: "3.9", os: "ubuntu-latest", session: "mypy" }
|
||||||
@ -176,6 +173,19 @@ jobs:
|
|||||||
name: logs-${{matrix.python}}-${{matrix.os}}-${{matrix.database}}
|
name: logs-${{matrix.python}}-${{matrix.os}}-${{matrix.database}}
|
||||||
path: "./log/*.log"
|
path: "./log/*.log"
|
||||||
|
|
||||||
|
# burnettk created an account at https://app.snyk.io/org/kevin-jfx
|
||||||
|
# and added his SNYK_TOKEN secret under the spiff-arena repo.
|
||||||
|
snyk:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@master
|
||||||
|
- name: Run Snyk to check for vulnerabilities
|
||||||
|
uses: snyk/actions/python@master
|
||||||
|
with:
|
||||||
|
args: spiffworkflow-backend
|
||||||
|
env:
|
||||||
|
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||||
|
|
||||||
run_pre_commit_checks:
|
run_pre_commit_checks:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
defaults:
|
defaults:
|
||||||
@ -184,9 +194,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check out the repository
|
- name: Check out the repository
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v3.3.0
|
||||||
with:
|
|
||||||
# Disabling shallow clone is recommended for improving relevancy of reporting in sonarcloud
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4.2.0
|
uses: actions/setup-python@v4.2.0
|
||||||
with:
|
with:
|
||||||
@ -205,9 +212,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check out the repository
|
- name: Check out the repository
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v3.3.0
|
||||||
with:
|
|
||||||
# Disabling shallow clone is recommended for improving relevancy of reporting in sonarcloud
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Checkout Samples
|
- name: Checkout Samples
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
@ -281,7 +285,7 @@ jobs:
|
|||||||
# so just skip everything but main
|
# so just skip everything but main
|
||||||
if: github.ref_name == 'main'
|
if: github.ref_name == 'main'
|
||||||
with:
|
with:
|
||||||
projectBaseDir: spiffworkflow-frontend
|
projectBaseDir: spiffworkflow-backend
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,6 @@
|
|||||||
pyrightconfig.json
|
pyrightconfig.json
|
||||||
.idea/
|
.idea/
|
||||||
t
|
t
|
||||||
|
*~
|
||||||
|
.dccache
|
||||||
|
*~
|
55
Jenkinsfile
vendored
55
Jenkinsfile
vendored
@ -32,6 +32,11 @@ pipeline {
|
|||||||
description: 'ID of Jenkins credential for Docker registry.',
|
description: 'ID of Jenkins credential for Docker registry.',
|
||||||
defaultValue: params.DOCKER_CRED_ID ?: 'MISSING'
|
defaultValue: params.DOCKER_CRED_ID ?: 'MISSING'
|
||||||
)
|
)
|
||||||
|
string(
|
||||||
|
name: 'DISCORD_WEBHOOK_CRED',
|
||||||
|
description: 'Name of cretential with Discord webhook',
|
||||||
|
defaultValue: params.DISCORD_WEBHOOK_CRED ?: "",
|
||||||
|
)
|
||||||
booleanParam(
|
booleanParam(
|
||||||
name: 'PUBLISH',
|
name: 'PUBLISH',
|
||||||
description: 'Publish built Docker images.',
|
description: 'Publish built Docker images.',
|
||||||
@ -61,6 +66,16 @@ pipeline {
|
|||||||
image.push(env.DOCKER_TAG)
|
image.push(env.DOCKER_TAG)
|
||||||
}
|
}
|
||||||
} }
|
} }
|
||||||
|
post {
|
||||||
|
success { script {
|
||||||
|
if (params.DISCORD_WEBHOOK_CRED) {
|
||||||
|
discordNotify(
|
||||||
|
header: 'SpiffWorkflow Docker image published!',
|
||||||
|
cred: params.DISCORD_WEBHOOK_CRED,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // stages
|
} // stages
|
||||||
post {
|
post {
|
||||||
@ -68,3 +83,43 @@ pipeline {
|
|||||||
cleanup { cleanWs() }
|
cleanup { cleanWs() }
|
||||||
} // post
|
} // post
|
||||||
} // pipeline
|
} // pipeline
|
||||||
|
|
||||||
|
def discordNotify(Map args=[:]) {
|
||||||
|
def opts = [
|
||||||
|
header: args.header ?: 'Deployment successful!',
|
||||||
|
title: args.title ?: "${env.JOB_NAME}#${env.BUILD_NUMBER}",
|
||||||
|
cred: args.cred ?: null,
|
||||||
|
]
|
||||||
|
def repo = [
|
||||||
|
url: GIT_URL.minus('.git'),
|
||||||
|
branch: GIT_BRANCH.minus('origin/'),
|
||||||
|
commit: GIT_COMMIT.take(8),
|
||||||
|
prev: (
|
||||||
|
env.GIT_PREVIOUS_SUCCESSFUL_COMMIT ?: env.GIT_PREVIOUS_COMMIT ?: 'master'
|
||||||
|
).take(8),
|
||||||
|
]
|
||||||
|
wrap([$class: 'BuildUser']) {
|
||||||
|
BUILD_USER_ID = env.BUILD_USER_ID
|
||||||
|
}
|
||||||
|
withCredentials([
|
||||||
|
string(
|
||||||
|
credentialsId: opts.cred,
|
||||||
|
variable: 'DISCORD_WEBHOOK',
|
||||||
|
),
|
||||||
|
]) {
|
||||||
|
discordSend(
|
||||||
|
link: env.BUILD_URL,
|
||||||
|
result: currentBuild.currentResult,
|
||||||
|
webhookURL: env.DISCORD_WEBHOOK,
|
||||||
|
title: opts.title,
|
||||||
|
description: """
|
||||||
|
${opts.header}
|
||||||
|
Image: [`${params.DOCKER_NAME}:${params.DOCKER_TAG}`](https://hub.docker.com/r/${params.DOCKER_NAME}/tags?name=${params.DOCKER_TAG})
|
||||||
|
Branch: [`${repo.branch}`](${repo.url}/commits/${repo.branch})
|
||||||
|
Commit: [`${repo.commit}`](${repo.url}/commit/${repo.commit})
|
||||||
|
Diff: [`${repo.prev}...${repo.commit}`](${repo.url}/compare/${repo.prev}...${repo.commit})
|
||||||
|
By: [`${BUILD_USER_ID}`](${repo.url}/commits?author=${BUILD_USER_ID})
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
"""__init.py__"""
|
|
@ -72,7 +72,7 @@ def convert_timer_expressions(dct):
|
|||||||
def add_default_condition_to_cond_task_specs(dct):
|
def add_default_condition_to_cond_task_specs(dct):
|
||||||
|
|
||||||
for spec in [ts for ts in dct['spec']['task_specs'].values() if ts['typename'] == 'ExclusiveGateway']:
|
for spec in [ts for ts in dct['spec']['task_specs'].values() if ts['typename'] == 'ExclusiveGateway']:
|
||||||
if (None, spec['default_task_spec']) not in spec['cond_task_specs']:
|
if spec['default_task_spec'] is not None and (None, spec['default_task_spec']) not in spec['cond_task_specs']:
|
||||||
spec['cond_task_specs'].append({'condition': None, 'task_spec': spec['default_task_spec']})
|
spec['cond_task_specs'].append({'condition': None, 'task_spec': spec['default_task_spec']})
|
||||||
|
|
||||||
def create_data_objects_and_io_specs(dct):
|
def create_data_objects_and_io_specs(dct):
|
||||||
@ -111,3 +111,14 @@ def check_multiinstance(dct):
|
|||||||
specs = [ spec for spec in dct['spec']['task_specs'].values() if 'prevtaskclass' in spec ]
|
specs = [ spec for spec in dct['spec']['task_specs'].values() if 'prevtaskclass' in spec ]
|
||||||
if len(specs) > 0:
|
if len(specs) > 0:
|
||||||
raise VersionMigrationError("This workflow cannot be migrated because it contains MultiInstance Tasks")
|
raise VersionMigrationError("This workflow cannot be migrated because it contains MultiInstance Tasks")
|
||||||
|
|
||||||
|
def remove_loop_reset(dct):
|
||||||
|
task_specs = [spec for spec in dct['spec']['task_specs'].values() if spec['typename'] == 'LoopResetTask']
|
||||||
|
for spec in task_specs:
|
||||||
|
if spec['typename'] == 'LoopResetTask':
|
||||||
|
tasks = [t for t in dct['tasks'].values() if t['task_spec'] == spec['name']]
|
||||||
|
for task in tasks:
|
||||||
|
dct['tasks'].pop(task['id'])
|
||||||
|
parent = dct['tasks'].get(task['parent'])
|
||||||
|
parent['children'] = [c for c in parent['children'] if c != task['id']]
|
||||||
|
dct['spec']['task_specs'].pop(spec['name'])
|
||||||
|
@ -6,6 +6,7 @@ from .version_1_2 import (
|
|||||||
add_default_condition_to_cond_task_specs,
|
add_default_condition_to_cond_task_specs,
|
||||||
create_data_objects_and_io_specs,
|
create_data_objects_and_io_specs,
|
||||||
check_multiinstance,
|
check_multiinstance,
|
||||||
|
remove_loop_reset,
|
||||||
)
|
)
|
||||||
|
|
||||||
def from_version_1_1(old):
|
def from_version_1_1(old):
|
||||||
@ -23,12 +24,18 @@ def from_version_1_1(old):
|
|||||||
Data inputs and outputs on process specs were moved inside a BPMNIOSpecification, and
|
Data inputs and outputs on process specs were moved inside a BPMNIOSpecification, and
|
||||||
are now TaskDataReferences; BpmnDataSpecifications that referred to Data Objects are
|
are now TaskDataReferences; BpmnDataSpecifications that referred to Data Objects are
|
||||||
now DataObjects.
|
now DataObjects.
|
||||||
|
|
||||||
|
Multiinstance tasks were completely refactored, in a way that is simply too difficult to
|
||||||
|
migrate.
|
||||||
|
|
||||||
|
Loop reset tasks were removed.
|
||||||
"""
|
"""
|
||||||
new = deepcopy(old)
|
new = deepcopy(old)
|
||||||
convert_timer_expressions(new)
|
convert_timer_expressions(new)
|
||||||
add_default_condition_to_cond_task_specs(new)
|
add_default_condition_to_cond_task_specs(new)
|
||||||
create_data_objects_and_io_specs(new)
|
create_data_objects_and_io_specs(new)
|
||||||
check_multiinstance(new)
|
check_multiinstance(new)
|
||||||
|
remove_loop_reset(new)
|
||||||
new['VERSION'] = "1.2"
|
new['VERSION'] = "1.2"
|
||||||
return new
|
return new
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
from ..specs.BpmnProcessSpec import BpmnProcessSpec
|
from ..specs.BpmnProcessSpec import BpmnProcessSpec
|
||||||
from ..specs.MultiInstanceTask import MultiInstanceTask
|
|
||||||
from ..specs.events.IntermediateEvent import _BoundaryEventParent
|
from ..specs.events.IntermediateEvent import _BoundaryEventParent
|
||||||
|
|
||||||
from .helpers.spec import WorkflowSpecConverter
|
from .helpers.spec import WorkflowSpecConverter
|
||||||
|
@ -2,7 +2,6 @@ from .helpers.spec import TaskSpecConverter
|
|||||||
|
|
||||||
from ...specs.StartTask import StartTask
|
from ...specs.StartTask import StartTask
|
||||||
from ...specs.Simple import Simple
|
from ...specs.Simple import Simple
|
||||||
from ...specs.LoopResetTask import LoopResetTask
|
|
||||||
|
|
||||||
from ..specs.BpmnProcessSpec import _EndJoin
|
from ..specs.BpmnProcessSpec import _EndJoin
|
||||||
from ..specs.BpmnSpecMixin import _BpmnCondition
|
from ..specs.BpmnSpecMixin import _BpmnCondition
|
||||||
@ -27,8 +26,6 @@ from ..specs.events.IntermediateEvent import (
|
|||||||
ReceiveTask,
|
ReceiveTask,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ..workflow import BpmnWorkflow
|
|
||||||
|
|
||||||
|
|
||||||
class DefaultTaskSpecConverter(TaskSpecConverter):
|
class DefaultTaskSpecConverter(TaskSpecConverter):
|
||||||
|
|
||||||
@ -50,23 +47,6 @@ class StartTaskConverter(DefaultTaskSpecConverter):
|
|||||||
super().__init__(StartTask, registry)
|
super().__init__(StartTask, registry)
|
||||||
|
|
||||||
|
|
||||||
class LoopResetTaskConverter(DefaultTaskSpecConverter):
|
|
||||||
|
|
||||||
def __init__(self, registry):
|
|
||||||
super().__init__(LoopResetTask, registry)
|
|
||||||
|
|
||||||
def to_dict(self, spec):
|
|
||||||
dct = super().to_dict(spec)
|
|
||||||
dct['destination_id'] = str(spec.destination_id)
|
|
||||||
dct['destination_spec_name'] = spec.destination_spec_name
|
|
||||||
return dct
|
|
||||||
|
|
||||||
def from_dict(self, dct):
|
|
||||||
spec = self.task_spec_from_dict(dct)
|
|
||||||
spec.destination_id = self.registry.convert(spec.destination_id)
|
|
||||||
return spec
|
|
||||||
|
|
||||||
|
|
||||||
class EndJoinConverter(DefaultTaskSpecConverter):
|
class EndJoinConverter(DefaultTaskSpecConverter):
|
||||||
def __init__(self, registry):
|
def __init__(self, registry):
|
||||||
super().__init__(_EndJoin, registry)
|
super().__init__(_EndJoin, registry)
|
||||||
@ -317,7 +297,6 @@ DEFAULT_TASK_SPEC_CONVERTER_CLASSES = [
|
|||||||
SimpleTaskConverter,
|
SimpleTaskConverter,
|
||||||
StartTaskConverter,
|
StartTaskConverter,
|
||||||
EndJoinConverter,
|
EndJoinConverter,
|
||||||
LoopResetTaskConverter,
|
|
||||||
NoneTaskConverter,
|
NoneTaskConverter,
|
||||||
UserTaskConverter,
|
UserTaskConverter,
|
||||||
ManualTaskConverter,
|
ManualTaskConverter,
|
||||||
|
@ -246,7 +246,7 @@ class BpmnWorkflowSerializer:
|
|||||||
|
|
||||||
if isinstance(task_spec, SubWorkflowTask) and task_id in top_dct.get('subprocesses', {}):
|
if isinstance(task_spec, SubWorkflowTask) and task_id in top_dct.get('subprocesses', {}):
|
||||||
subprocess_spec = top.subprocess_specs[task_spec.spec]
|
subprocess_spec = top.subprocess_specs[task_spec.spec]
|
||||||
subprocess = self.wf_class(subprocess_spec, {}, name=task_spec.name, parent=process)
|
subprocess = self.wf_class(subprocess_spec, {}, name=task_spec.name, parent=process, deserializing=True)
|
||||||
subprocess_dct = top_dct['subprocesses'].get(task_id, {})
|
subprocess_dct = top_dct['subprocesses'].get(task_id, {})
|
||||||
subprocess.data = self.data_converter.restore(subprocess_dct.pop('data'))
|
subprocess.data = self.data_converter.restore(subprocess_dct.pop('data'))
|
||||||
subprocess.success = subprocess_dct.pop('success')
|
subprocess.success = subprocess_dct.pop('success')
|
||||||
@ -254,8 +254,12 @@ class BpmnWorkflowSerializer:
|
|||||||
subprocess.completed_event.connect(task_spec._on_subworkflow_completed, task)
|
subprocess.completed_event.connect(task_spec._on_subworkflow_completed, task)
|
||||||
top_level_workflow.subprocesses[task.id] = subprocess
|
top_level_workflow.subprocesses[task.id] = subprocess
|
||||||
|
|
||||||
for child in [ process_dct['tasks'][c] for c in task_dict['children'] ]:
|
for child_task_id in task_dict['children']:
|
||||||
self.task_tree_from_dict(process_dct, child['id'], task, process, top, top_dct)
|
if child_task_id in process_dct['tasks']:
|
||||||
|
child = 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")
|
||||||
|
|
||||||
return task
|
return task
|
||||||
|
|
||||||
|
@ -47,9 +47,10 @@ class _EndJoin(UnstructuredJoin):
|
|||||||
|
|
||||||
return force or len(waiting_tasks) == 0, waiting_tasks
|
return force or len(waiting_tasks) == 0, waiting_tasks
|
||||||
|
|
||||||
def _on_complete_hook(self, my_task):
|
def _run_hook(self, my_task):
|
||||||
super(_EndJoin, self)._on_complete_hook(my_task)
|
result = super(_EndJoin, self)._run_hook(my_task)
|
||||||
my_task.workflow.data.update(my_task.data)
|
my_task.workflow.data.update(my_task.data)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class BpmnProcessSpec(WorkflowSpec):
|
class BpmnProcessSpec(WorkflowSpec):
|
||||||
|
@ -110,11 +110,12 @@ class InclusiveGateway(MultiChoice, UnstructuredJoin):
|
|||||||
|
|
||||||
return complete, waiting_tasks
|
return complete, waiting_tasks
|
||||||
|
|
||||||
def _on_complete_hook(self, my_task):
|
def _run_hook(self, my_task):
|
||||||
outputs = self._get_matching_outputs(my_task)
|
outputs = self._get_matching_outputs(my_task)
|
||||||
if len(outputs) == 0:
|
if len(outputs) == 0:
|
||||||
raise WorkflowTaskException(f'No conditions satisfied on gateway', task=my_task)
|
raise WorkflowTaskException(f'No conditions satisfied on gateway', task=my_task)
|
||||||
my_task._sync_children(outputs, TaskState.FUTURE)
|
my_task._sync_children(outputs, TaskState.FUTURE)
|
||||||
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def spec_type(self):
|
def spec_type(self):
|
||||||
|
@ -29,14 +29,14 @@ class ScriptEngineTask(Simple, BpmnSpecMixin):
|
|||||||
"""Please override for specific Implementations, see ScriptTask below for an example"""
|
"""Please override for specific Implementations, see ScriptTask below for an example"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _on_complete_hook(self, task):
|
def _run_hook(self, task):
|
||||||
try:
|
try:
|
||||||
self._execute(task)
|
self._execute(task)
|
||||||
super(ScriptEngineTask, self)._on_complete_hook(task)
|
super(ScriptEngineTask, self)._run_hook(task)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
task._set_state(TaskState.WAITING)
|
task._set_state(TaskState.WAITING)
|
||||||
raise exc
|
raise exc
|
||||||
|
return True
|
||||||
|
|
||||||
class ScriptTask(ScriptEngineTask):
|
class ScriptTask(ScriptEngineTask):
|
||||||
|
|
||||||
|
@ -25,9 +25,6 @@ class SubWorkflowTask(BpmnSpecMixin):
|
|||||||
def spec_type(self):
|
def spec_type(self):
|
||||||
return 'Subprocess'
|
return 'Subprocess'
|
||||||
|
|
||||||
def _on_ready_hook(self, my_task):
|
|
||||||
super()._on_ready_hook(my_task)
|
|
||||||
|
|
||||||
def _on_subworkflow_completed(self, subworkflow, my_task):
|
def _on_subworkflow_completed(self, subworkflow, my_task):
|
||||||
self.update_data(my_task, subworkflow)
|
self.update_data(my_task, subworkflow)
|
||||||
my_task._set_state(TaskState.READY)
|
my_task._set_state(TaskState.READY)
|
||||||
|
@ -54,14 +54,14 @@ class UnstructuredJoin(Join, BpmnSpecMixin):
|
|||||||
last_changed = None
|
last_changed = None
|
||||||
thread_tasks = []
|
thread_tasks = []
|
||||||
for task in split_task._find_any(self):
|
for task in split_task._find_any(self):
|
||||||
# Ignore tasks from other threads.
|
|
||||||
if task.thread_id != my_task.thread_id:
|
if task.thread_id != my_task.thread_id:
|
||||||
|
# Ignore tasks from other threads. (Do we need this condition?)
|
||||||
continue
|
continue
|
||||||
# Ignore my outgoing branches.
|
|
||||||
if self.split_task and task._is_descendant_of(my_task):
|
|
||||||
continue
|
|
||||||
# For an inclusive join, this can happen - it's a future join
|
|
||||||
if not task.parent._is_finished():
|
if not task.parent._is_finished():
|
||||||
|
# For an inclusive join, this can happen - it's a future join
|
||||||
|
continue
|
||||||
|
if my_task._is_descendant_of(task):
|
||||||
|
# Skip ancestors (otherwise the branch this task is on will get dropped)
|
||||||
continue
|
continue
|
||||||
# We have found a matching instance.
|
# We have found a matching instance.
|
||||||
thread_tasks.append(task)
|
thread_tasks.append(task)
|
||||||
@ -77,20 +77,13 @@ class UnstructuredJoin(Join, BpmnSpecMixin):
|
|||||||
for task in thread_tasks:
|
for task in thread_tasks:
|
||||||
collected_data.update(task.data)
|
collected_data.update(task.data)
|
||||||
|
|
||||||
# Mark the identified task instances as COMPLETED. The exception
|
|
||||||
# is the most recently changed task, for which we assume READY.
|
|
||||||
# By setting the state to READY only, we allow for calling
|
|
||||||
# :class:`Task.complete()`, which leads to the task tree being
|
|
||||||
# (re)built underneath the node.
|
|
||||||
for task in thread_tasks:
|
for task in thread_tasks:
|
||||||
if task == last_changed:
|
if task != last_changed:
|
||||||
task.data.update(collected_data)
|
task._set_state(TaskState.CANCELLED)
|
||||||
self.entered_event.emit(my_task.workflow, my_task)
|
|
||||||
task._ready()
|
|
||||||
else:
|
|
||||||
task._set_state(TaskState.COMPLETED)
|
|
||||||
task._drop_children()
|
task._drop_children()
|
||||||
|
else:
|
||||||
|
task.data.update(collected_data)
|
||||||
|
|
||||||
def task_should_set_children_future(self, my_task):
|
def task_should_set_children_future(self, my_task):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
|
|
||||||
from .event_types import ThrowingEvent, CatchingEvent
|
from .event_types import ThrowingEvent, CatchingEvent
|
||||||
from .event_definitions import CycleTimerEventDefinition
|
|
||||||
from ..BpmnSpecMixin import BpmnSpecMixin
|
from ..BpmnSpecMixin import BpmnSpecMixin
|
||||||
from ....specs.Simple import Simple
|
from ....specs.Simple import Simple
|
||||||
from ....task import TaskState
|
from ....task import TaskState
|
||||||
@ -67,13 +66,15 @@ class _BoundaryEventParent(Simple, BpmnSpecMixin):
|
|||||||
def spec_type(self):
|
def spec_type(self):
|
||||||
return 'Boundary Event Parent'
|
return 'Boundary Event Parent'
|
||||||
|
|
||||||
def _on_ready_hook(self, my_task):
|
def _run_hook(self, my_task):
|
||||||
|
|
||||||
# Clear any events that our children might have received and
|
# Clear any events that our children might have received and
|
||||||
# wait for new events
|
# wait for new events
|
||||||
for child in my_task.children:
|
for child in my_task.children:
|
||||||
if isinstance(child.task_spec, BoundaryEvent):
|
if isinstance(child.task_spec, BoundaryEvent):
|
||||||
child.task_spec.event_definition.reset(child)
|
child.task_spec.event_definition.reset(child)
|
||||||
|
child._set_state(TaskState.WAITING)
|
||||||
|
return True
|
||||||
|
|
||||||
def _child_complete_hook(self, child_task):
|
def _child_complete_hook(self, child_task):
|
||||||
|
|
||||||
@ -123,7 +124,7 @@ class BoundaryEvent(CatchingEvent):
|
|||||||
super(BoundaryEvent, self).catch(my_task, event_definition)
|
super(BoundaryEvent, self).catch(my_task, event_definition)
|
||||||
# Would love to get rid of this statement and manage in the workflow
|
# Would love to get rid of this statement and manage in the workflow
|
||||||
# However, it is not really compatible with how boundary events work.
|
# However, it is not really compatible with how boundary events work.
|
||||||
my_task.complete()
|
my_task.run()
|
||||||
|
|
||||||
|
|
||||||
class EventBasedGateway(CatchingEvent):
|
class EventBasedGateway(CatchingEvent):
|
||||||
@ -135,8 +136,8 @@ class EventBasedGateway(CatchingEvent):
|
|||||||
def _predict_hook(self, my_task):
|
def _predict_hook(self, my_task):
|
||||||
my_task._sync_children(self.outputs, state=TaskState.MAYBE)
|
my_task._sync_children(self.outputs, state=TaskState.MAYBE)
|
||||||
|
|
||||||
def _on_complete_hook(self, my_task):
|
def _on_ready_hook(self, my_task):
|
||||||
|
seen_events = my_task.internal_data.get('seen_events', [])
|
||||||
for child in my_task.children:
|
for child in my_task.children:
|
||||||
if not child.task_spec.event_definition.has_fired(child):
|
if child.task_spec.event_definition not in seen_events:
|
||||||
child.cancel()
|
child.cancel()
|
||||||
return super()._on_complete_hook(my_task)
|
|
@ -20,13 +20,14 @@
|
|||||||
import re
|
import re
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from calendar import monthrange
|
from calendar import monthrange
|
||||||
from time import timezone as tzoffset
|
from time import timezone as tzoffset, altzone as dstoffset, daylight as isdst
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
from SpiffWorkflow.exceptions import SpiffWorkflowException, WorkflowException
|
from SpiffWorkflow.exceptions import WorkflowException
|
||||||
from SpiffWorkflow.task import TaskState
|
from SpiffWorkflow.task import TaskState
|
||||||
|
|
||||||
LOCALTZ = timezone(timedelta(seconds=-1 * tzoffset))
|
seconds_from_utc = dstoffset if isdst else tzoffset
|
||||||
|
LOCALTZ = timezone(timedelta(seconds=-1 * seconds_from_utc))
|
||||||
|
|
||||||
|
|
||||||
class EventDefinition(object):
|
class EventDefinition(object):
|
||||||
@ -452,42 +453,38 @@ class CycleTimerEventDefinition(TimerEventDefinition):
|
|||||||
def event_type(self):
|
def event_type(self):
|
||||||
return 'Cycle Timer'
|
return 'Cycle Timer'
|
||||||
|
|
||||||
def has_fired(self, my_task):
|
def cycle_complete(self, my_task):
|
||||||
|
|
||||||
if not my_task._get_internal_data('event_fired'):
|
event_value = my_task._get_internal_data('event_value')
|
||||||
# Only check for the next cycle when the event has not fired to prevent cycles from being skipped.
|
if event_value is None:
|
||||||
event_value = my_task._get_internal_data('event_value')
|
# Don't necessarily like this, but it's a lot more staightforward than trying to only create
|
||||||
if event_value is None:
|
# a child task on loop iterations after the first
|
||||||
expression = my_task.workflow.script_engine.evaluate(my_task, self.expression)
|
my_task._drop_children()
|
||||||
cycles, start, duration = TimerEventDefinition.parse_iso_recurring_interval(expression)
|
expression = my_task.workflow.script_engine.evaluate(my_task, self.expression)
|
||||||
event_value = {'cycles': cycles, 'next': start.isoformat(), 'duration': duration.total_seconds()}
|
cycles, start, duration = TimerEventDefinition.parse_iso_recurring_interval(expression)
|
||||||
|
event_value = {'cycles': cycles, 'next': start.isoformat(), 'duration': duration.total_seconds()}
|
||||||
|
|
||||||
if event_value['cycles'] > 0:
|
# When the next timer event passes, return True to allow the parent task to generate another child
|
||||||
next_event = datetime.fromisoformat(event_value['next'])
|
# Use event fired to indicate that this timer has completed all cycles and the task can be completed
|
||||||
if next_event < datetime.now(timezone.utc):
|
ready = False
|
||||||
my_task._set_internal_data(event_fired=True)
|
if event_value['cycles'] != 0:
|
||||||
event_value['next'] = (next_event + timedelta(seconds=event_value['duration'])).isoformat()
|
next_event = datetime.fromisoformat(event_value['next'])
|
||||||
|
if next_event < datetime.now(timezone.utc):
|
||||||
|
event_value['next'] = (next_event + timedelta(seconds=event_value['duration'])).isoformat()
|
||||||
|
event_value['cycles'] -= 1
|
||||||
|
ready = True
|
||||||
|
else:
|
||||||
|
my_task.internal_data.pop('event_value', None)
|
||||||
|
my_task.internal_data['event_fired'] = True
|
||||||
|
|
||||||
my_task._set_internal_data(event_value=event_value)
|
my_task._set_internal_data(event_value=event_value)
|
||||||
|
return ready
|
||||||
return my_task._get_internal_data('event_fired', False)
|
|
||||||
|
|
||||||
def timer_value(self, my_task):
|
def timer_value(self, my_task):
|
||||||
event_value = my_task._get_internal_data('event_value')
|
event_value = my_task._get_internal_data('event_value')
|
||||||
if event_value is not None and event_value['cycles'] > 0:
|
if event_value is not None and event_value['cycles'] != 0:
|
||||||
return event_value['next']
|
return event_value['next']
|
||||||
|
|
||||||
def complete(self, my_task):
|
|
||||||
event_value = my_task._get_internal_data('event_value')
|
|
||||||
if event_value is not None and event_value['cycles'] == 0:
|
|
||||||
my_task.internal_data.pop('event_value')
|
|
||||||
return True
|
|
||||||
|
|
||||||
def complete_cycle(self, my_task):
|
|
||||||
# Only increment when the task completes
|
|
||||||
if my_task._get_internal_data('event_value') is not None:
|
|
||||||
my_task.internal_data['event_value']['cycles'] -= 1
|
|
||||||
|
|
||||||
|
|
||||||
class MultipleEventDefinition(EventDefinition):
|
class MultipleEventDefinition(EventDefinition):
|
||||||
|
|
||||||
@ -504,11 +501,10 @@ class MultipleEventDefinition(EventDefinition):
|
|||||||
|
|
||||||
seen_events = my_task.internal_data.get('seen_events', [])
|
seen_events = my_task.internal_data.get('seen_events', [])
|
||||||
for event in self.event_definitions:
|
for event in self.event_definitions:
|
||||||
if isinstance(event, (TimerEventDefinition, CycleTimerEventDefinition)):
|
if isinstance(event, TimerEventDefinition):
|
||||||
child = [c for c in my_task.children if c.task_spec.event_definition == event]
|
child = [c for c in my_task.children if c.task_spec.event_definition == event]
|
||||||
child[0].task_spec._update_hook(child[0])
|
child[0].task_spec._update_hook(child[0])
|
||||||
child[0]._set_state(TaskState.MAYBE)
|
if event.has_fired(child[0]):
|
||||||
if event.has_fired(my_task):
|
|
||||||
seen_events.append(event)
|
seen_events.append(event)
|
||||||
|
|
||||||
if self.parallel:
|
if self.parallel:
|
||||||
|
@ -57,21 +57,22 @@ class CatchingEvent(Simple, BpmnSpecMixin):
|
|||||||
|
|
||||||
if self.event_definition.has_fired(my_task):
|
if self.event_definition.has_fired(my_task):
|
||||||
return True
|
return True
|
||||||
else:
|
elif isinstance(self.event_definition, CycleTimerEventDefinition):
|
||||||
|
if self.event_definition.cycle_complete(my_task):
|
||||||
|
for output in self.outputs:
|
||||||
|
child = my_task._add_child(output, TaskState.READY)
|
||||||
|
child.task_spec._predict(child, mask=TaskState.READY|TaskState.PREDICTED_MASK)
|
||||||
|
if my_task.state != TaskState.WAITING:
|
||||||
|
my_task._set_state(TaskState.WAITING)
|
||||||
|
elif my_task.state != TaskState.WAITING:
|
||||||
my_task._set_state(TaskState.WAITING)
|
my_task._set_state(TaskState.WAITING)
|
||||||
|
|
||||||
def _on_complete_hook(self, my_task):
|
def _run_hook(self, my_task):
|
||||||
|
|
||||||
if isinstance(self.event_definition, MessageEventDefinition):
|
if isinstance(self.event_definition, MessageEventDefinition):
|
||||||
self.event_definition.update_task_data(my_task)
|
self.event_definition.update_task_data(my_task)
|
||||||
elif isinstance(self.event_definition, CycleTimerEventDefinition):
|
|
||||||
self.event_definition.complete_cycle(my_task)
|
|
||||||
if not self.event_definition.complete(my_task):
|
|
||||||
for output in self.outputs:
|
|
||||||
my_task._add_child(output)
|
|
||||||
my_task._set_state(TaskState.WAITING)
|
|
||||||
self.event_definition.reset(my_task)
|
self.event_definition.reset(my_task)
|
||||||
super(CatchingEvent, self)._on_complete_hook(my_task)
|
return super(CatchingEvent, self)._run_hook(my_task)
|
||||||
|
|
||||||
# This fixes the problem of boundary events remaining cancelled if the task is reused.
|
# 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
|
# It pains me to add these methods, but unless we can get rid of the loop reset task we're stuck
|
||||||
@ -95,6 +96,7 @@ class ThrowingEvent(Simple, BpmnSpecMixin):
|
|||||||
super(ThrowingEvent, self).__init__(wf_spec, name, **kwargs)
|
super(ThrowingEvent, self).__init__(wf_spec, name, **kwargs)
|
||||||
self.event_definition = event_definition
|
self.event_definition = event_definition
|
||||||
|
|
||||||
def _on_complete_hook(self, my_task):
|
def _run_hook(self, my_task):
|
||||||
super(ThrowingEvent, self)._on_complete_hook(my_task)
|
super(ThrowingEvent, self)._run_hook(my_task)
|
||||||
self.event_definition.throw(my_task)
|
self.event_definition.throw(my_task)
|
||||||
|
return True
|
||||||
|
@ -29,7 +29,7 @@ from .specs.events.StartEvent import StartEvent
|
|||||||
from .specs.SubWorkflowTask import CallActivity
|
from .specs.SubWorkflowTask import CallActivity
|
||||||
from ..task import TaskState, Task
|
from ..task import TaskState, Task
|
||||||
from ..workflow import Workflow
|
from ..workflow import Workflow
|
||||||
from ..exceptions import WorkflowException, WorkflowTaskException
|
from ..exceptions import TaskNotFoundException, WorkflowException, WorkflowTaskException
|
||||||
|
|
||||||
|
|
||||||
class BpmnMessage:
|
class BpmnMessage:
|
||||||
@ -251,7 +251,7 @@ class BpmnWorkflow(Workflow):
|
|||||||
for task in engine_steps:
|
for task in engine_steps:
|
||||||
if will_complete_task is not None:
|
if will_complete_task is not None:
|
||||||
will_complete_task(task)
|
will_complete_task(task)
|
||||||
task.complete()
|
task.run()
|
||||||
if did_complete_task is not None:
|
if did_complete_task is not None:
|
||||||
did_complete_task(task)
|
did_complete_task(task)
|
||||||
if task.task_spec.name == exit_at:
|
if task.task_spec.name == exit_at:
|
||||||
@ -271,7 +271,10 @@ class BpmnWorkflow(Workflow):
|
|||||||
for my_task in self.get_tasks(TaskState.WAITING):
|
for my_task in self.get_tasks(TaskState.WAITING):
|
||||||
if will_refresh_task is not None:
|
if will_refresh_task is not None:
|
||||||
will_refresh_task(my_task)
|
will_refresh_task(my_task)
|
||||||
my_task.task_spec._update(my_task)
|
# This seems redundant, but the state could have been updated by another waiting task and no longer be waiting.
|
||||||
|
# Someday, I would like to get rid of this method, and also do_engine_steps
|
||||||
|
if my_task.state == TaskState.WAITING:
|
||||||
|
my_task.task_spec._update(my_task)
|
||||||
if did_refresh_task is not None:
|
if did_refresh_task is not None:
|
||||||
did_refresh_task(my_task)
|
did_refresh_task(my_task)
|
||||||
|
|
||||||
@ -279,10 +282,15 @@ class BpmnWorkflow(Workflow):
|
|||||||
return [t for t in self.get_tasks(workflow=workflow) if t.task_spec.name == name]
|
return [t for t in self.get_tasks(workflow=workflow) if t.task_spec.name == name]
|
||||||
|
|
||||||
def get_tasks(self, state=TaskState.ANY_MASK, workflow=None):
|
def get_tasks(self, state=TaskState.ANY_MASK, workflow=None):
|
||||||
|
# Now that I've revisited and had to ask myself what the hell was I doing, I realize I should comment this
|
||||||
tasks = []
|
tasks = []
|
||||||
top = self._get_outermost_workflow()
|
top = self._get_outermost_workflow()
|
||||||
wf = workflow or top
|
# I think it makes more sense to start with the current workflow, which is probably going to be the top
|
||||||
for task in Workflow.get_tasks(wf):
|
# most of the time anyway
|
||||||
|
wf = workflow or self
|
||||||
|
# We can't filter the iterator on the state because we have to subprocesses, and the subprocess task will
|
||||||
|
# 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)
|
subprocess = top.subprocesses.get(task.id)
|
||||||
if subprocess is not None:
|
if subprocess is not None:
|
||||||
tasks.extend(subprocess.get_tasks(state, subprocess))
|
tasks.extend(subprocess.get_tasks(state, subprocess))
|
||||||
@ -290,42 +298,28 @@ class BpmnWorkflow(Workflow):
|
|||||||
tasks.append(task)
|
tasks.append(task)
|
||||||
return tasks
|
return tasks
|
||||||
|
|
||||||
def _find_task(self, task_id):
|
def get_task_from_id(self, task_id, workflow=None):
|
||||||
if task_id is None:
|
for task in self.get_tasks(workflow=workflow):
|
||||||
raise WorkflowException('task_id is None', task_spec=self.spec)
|
|
||||||
for task in self.get_tasks():
|
|
||||||
if task.id == task_id:
|
if task.id == task_id:
|
||||||
return task
|
return task
|
||||||
raise WorkflowException(f'A task with the given task_id ({task_id}) was not found', task_spec=self.spec)
|
raise TaskNotFoundException(f'A task with the given task_id ({task_id}) was not found', task_spec=self.spec)
|
||||||
|
|
||||||
def complete_task_from_id(self, task_id):
|
def get_ready_user_tasks(self, lane=None, workflow=None):
|
||||||
# I don't even know why we use this stupid function instead of calling task.complete,
|
|
||||||
# since all it does is search the task tree and call the method
|
|
||||||
task = self._find_task(task_id)
|
|
||||||
return task.complete()
|
|
||||||
|
|
||||||
def reset_task_from_id(self, task_id):
|
|
||||||
task = self._find_task(task_id)
|
|
||||||
if task.workflow.last_task and task.workflow.last_task.data:
|
|
||||||
data = task.workflow.last_task.data
|
|
||||||
return task.reset_token(data)
|
|
||||||
|
|
||||||
def get_ready_user_tasks(self,lane=None):
|
|
||||||
"""Returns a list of User Tasks that are READY for user action"""
|
"""Returns a list of User Tasks that are READY for user action"""
|
||||||
if lane is not None:
|
if lane is not None:
|
||||||
return [t for t in self.get_tasks(TaskState.READY)
|
return [t for t in self.get_tasks(TaskState.READY, workflow)
|
||||||
if (not self._is_engine_task(t.task_spec))
|
if (not self._is_engine_task(t.task_spec))
|
||||||
and (t.task_spec.lane == lane)]
|
and (t.task_spec.lane == lane)]
|
||||||
else:
|
else:
|
||||||
return [t for t in self.get_tasks(TaskState.READY)
|
return [t for t in self.get_tasks(TaskState.READY, workflow)
|
||||||
if not self._is_engine_task(t.task_spec)]
|
if not self._is_engine_task(t.task_spec)]
|
||||||
|
|
||||||
def get_waiting_tasks(self):
|
def get_waiting_tasks(self, workflow=None):
|
||||||
"""Returns a list of all WAITING tasks"""
|
"""Returns a list of all WAITING tasks"""
|
||||||
return self.get_tasks(TaskState.WAITING)
|
return self.get_tasks(TaskState.WAITING, workflow)
|
||||||
|
|
||||||
def get_catching_tasks(self):
|
def get_catching_tasks(self, workflow=None):
|
||||||
return [ task for task in self.get_tasks() if isinstance(task.task_spec, CatchingEvent) ]
|
return [task for task in self.get_tasks(workflow=workflow) if isinstance(task.task_spec, CatchingEvent)]
|
||||||
|
|
||||||
def _is_engine_task(self, task_spec):
|
def _is_engine_task(self, task_spec):
|
||||||
return (not hasattr(task_spec, 'is_engine_task') or task_spec.is_engine_task())
|
return (not hasattr(task_spec, 'is_engine_task') or task_spec.is_engine_task())
|
||||||
|
@ -5,10 +5,7 @@ from ..specs.model import DecisionTable, Rule, HitPolicy
|
|||||||
from ..specs.model import Input, InputEntry, Output, OutputEntry
|
from ..specs.model import Input, InputEntry, Output, OutputEntry
|
||||||
from ..engine.DMNEngine import DMNEngine
|
from ..engine.DMNEngine import DMNEngine
|
||||||
|
|
||||||
class BusinessRuleTaskConverter(TaskSpecConverter):
|
class BaseBusinessRuleTaskConverter(TaskSpecConverter):
|
||||||
|
|
||||||
def __init__(self, registry):
|
|
||||||
super().__init__(BusinessRuleTask, registry)
|
|
||||||
|
|
||||||
def to_dict(self, spec):
|
def to_dict(self, spec):
|
||||||
dct = self.get_default_attributes(spec)
|
dct = self.get_default_attributes(spec)
|
||||||
@ -98,3 +95,8 @@ class BusinessRuleTaskConverter(TaskSpecConverter):
|
|||||||
rule.outputEntries = [self.output_entry_from_dict(entry, outputs)
|
rule.outputEntries = [self.output_entry_from_dict(entry, outputs)
|
||||||
for entry in dct['output_entries']]
|
for entry in dct['output_entries']]
|
||||||
return rule
|
return rule
|
||||||
|
|
||||||
|
|
||||||
|
class BusinessRuleTaskConverter(BaseBusinessRuleTaskConverter):
|
||||||
|
def __init__(self, registry):
|
||||||
|
super().__init__(BusinessRuleTask, registry)
|
@ -1,5 +1,4 @@
|
|||||||
from SpiffWorkflow.exceptions import WorkflowTaskException, WorkflowException, \
|
from SpiffWorkflow.exceptions import WorkflowTaskException, SpiffWorkflowException
|
||||||
SpiffWorkflowException
|
|
||||||
|
|
||||||
from ...specs.Simple import Simple
|
from ...specs.Simple import Simple
|
||||||
|
|
||||||
@ -17,7 +16,6 @@ class BusinessRuleTask(Simple, BpmnSpecMixin):
|
|||||||
|
|
||||||
def __init__(self, wf_spec, name, dmnEngine, **kwargs):
|
def __init__(self, wf_spec, name, dmnEngine, **kwargs):
|
||||||
super().__init__(wf_spec, name, **kwargs)
|
super().__init__(wf_spec, name, **kwargs)
|
||||||
|
|
||||||
self.dmnEngine = dmnEngine
|
self.dmnEngine = dmnEngine
|
||||||
self.resDict = None
|
self.resDict = None
|
||||||
|
|
||||||
@ -25,11 +23,10 @@ class BusinessRuleTask(Simple, BpmnSpecMixin):
|
|||||||
def spec_class(self):
|
def spec_class(self):
|
||||||
return 'Business Rule Task'
|
return 'Business Rule Task'
|
||||||
|
|
||||||
def _on_complete_hook(self, my_task):
|
def _run_hook(self, my_task):
|
||||||
try:
|
try:
|
||||||
my_task.data = DeepMerge.merge(my_task.data,
|
my_task.data = DeepMerge.merge(my_task.data, self.dmnEngine.result(my_task))
|
||||||
self.dmnEngine.result(my_task))
|
super(BusinessRuleTask, self)._run_hook(my_task)
|
||||||
super(BusinessRuleTask, self)._on_complete_hook(my_task)
|
|
||||||
except SpiffWorkflowException as we:
|
except SpiffWorkflowException as we:
|
||||||
we.add_note(f"Business Rule Task '{my_task.task_spec.description}'.")
|
we.add_note(f"Business Rule Task '{my_task.task_spec.description}'.")
|
||||||
raise we
|
raise we
|
||||||
@ -37,4 +34,4 @@ class BusinessRuleTask(Simple, BpmnSpecMixin):
|
|||||||
error = WorkflowTaskException(str(e), task=my_task)
|
error = WorkflowTaskException(str(e), task=my_task)
|
||||||
error.add_note(f"Business Rule Task '{my_task.task_spec.description}'.")
|
error.add_note(f"Business Rule Task '{my_task.task_spec.description}'.")
|
||||||
raise error
|
raise error
|
||||||
|
return True
|
||||||
|
@ -129,3 +129,7 @@ class WorkflowTaskException(WorkflowException):
|
|||||||
|
|
||||||
class StorageException(SpiffWorkflowException):
|
class StorageException(SpiffWorkflowException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TaskNotFoundException(WorkflowException):
|
||||||
|
pass
|
||||||
|
@ -16,9 +16,7 @@ from builtins import object
|
|||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
|
|
||||||
import re
|
|
||||||
from .. import operators
|
from .. import operators
|
||||||
from .. import specs
|
|
||||||
from ..specs.AcquireMutex import AcquireMutex
|
from ..specs.AcquireMutex import AcquireMutex
|
||||||
from ..specs.Cancel import Cancel
|
from ..specs.Cancel import Cancel
|
||||||
from ..specs.CancelTask import CancelTask
|
from ..specs.CancelTask import CancelTask
|
||||||
@ -41,7 +39,6 @@ from ..specs.ThreadSplit import ThreadSplit
|
|||||||
from ..specs.Transform import Transform
|
from ..specs.Transform import Transform
|
||||||
from ..specs.Trigger import Trigger
|
from ..specs.Trigger import Trigger
|
||||||
from ..specs.WorkflowSpec import WorkflowSpec
|
from ..specs.WorkflowSpec import WorkflowSpec
|
||||||
from ..specs.LoopResetTask import LoopResetTask
|
|
||||||
|
|
||||||
# Create a list of tag names out of the spec names.
|
# Create a list of tag names out of the spec names.
|
||||||
def spec_map():
|
def spec_map():
|
||||||
@ -68,7 +65,6 @@ def spec_map():
|
|||||||
'transform': Transform,
|
'transform': Transform,
|
||||||
'trigger': Trigger,
|
'trigger': Trigger,
|
||||||
'workflow-spec': WorkflowSpec,
|
'workflow-spec': WorkflowSpec,
|
||||||
'loop-reset-task': LoopResetTask,
|
|
||||||
'task': Simple,
|
'task': Simple,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,8 +22,7 @@ from base64 import b64encode, b64decode
|
|||||||
from ..workflow import Workflow
|
from ..workflow import Workflow
|
||||||
from ..util.impl import get_class
|
from ..util.impl import get_class
|
||||||
from ..task import Task
|
from ..task import Task
|
||||||
from ..operators import (Attrib, PathAttrib, Equal, NotEqual,
|
from ..operators import (Attrib, PathAttrib, Equal, NotEqual, Operator, GreaterThan, LessThan, Match)
|
||||||
Operator, GreaterThan, LessThan, Match)
|
|
||||||
from ..specs.base import TaskSpec
|
from ..specs.base import TaskSpec
|
||||||
from ..specs.AcquireMutex import AcquireMutex
|
from ..specs.AcquireMutex import AcquireMutex
|
||||||
from ..specs.Cancel import Cancel
|
from ..specs.Cancel import Cancel
|
||||||
@ -44,10 +43,8 @@ from ..specs.SubWorkflow import SubWorkflow
|
|||||||
from ..specs.ThreadStart import ThreadStart
|
from ..specs.ThreadStart import ThreadStart
|
||||||
from ..specs.ThreadMerge import ThreadMerge
|
from ..specs.ThreadMerge import ThreadMerge
|
||||||
from ..specs.ThreadSplit import ThreadSplit
|
from ..specs.ThreadSplit import ThreadSplit
|
||||||
from ..specs.Transform import Transform
|
|
||||||
from ..specs.Trigger import Trigger
|
from ..specs.Trigger import Trigger
|
||||||
from ..specs.WorkflowSpec import WorkflowSpec
|
from ..specs.WorkflowSpec import WorkflowSpec
|
||||||
from ..specs.LoopResetTask import LoopResetTask
|
|
||||||
from .base import Serializer
|
from .base import Serializer
|
||||||
from .exceptions import TaskNotSupportedError, MissingSpecError
|
from .exceptions import TaskNotSupportedError, MissingSpecError
|
||||||
import warnings
|
import warnings
|
||||||
@ -169,7 +166,6 @@ class DictionarySerializer(Serializer):
|
|||||||
s_state['defines'] = self.serialize_dict(spec.defines)
|
s_state['defines'] = self.serialize_dict(spec.defines)
|
||||||
s_state['pre_assign'] = self.serialize_list(spec.pre_assign)
|
s_state['pre_assign'] = self.serialize_list(spec.pre_assign)
|
||||||
s_state['post_assign'] = self.serialize_list(spec.post_assign)
|
s_state['post_assign'] = self.serialize_list(spec.post_assign)
|
||||||
s_state['locks'] = spec.locks[:]
|
|
||||||
|
|
||||||
# Note: Events are not serialized; this is documented in
|
# Note: Events are not serialized; this is documented in
|
||||||
# the TaskSpec API docs.
|
# the TaskSpec API docs.
|
||||||
@ -190,7 +186,6 @@ class DictionarySerializer(Serializer):
|
|||||||
spec.pre_assign = self.deserialize_list(s_state.get('pre_assign', []))
|
spec.pre_assign = self.deserialize_list(s_state.get('pre_assign', []))
|
||||||
spec.post_assign = self.deserialize_list(
|
spec.post_assign = self.deserialize_list(
|
||||||
s_state.get('post_assign', []))
|
s_state.get('post_assign', []))
|
||||||
spec.locks = s_state.get('locks', [])[:]
|
|
||||||
# We can't restore inputs and outputs yet because they may not be
|
# 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.
|
# deserialized yet. So keep the names, and resolve them in the end.
|
||||||
spec.inputs = s_state.get('inputs', [])[:]
|
spec.inputs = s_state.get('inputs', [])[:]
|
||||||
@ -302,18 +297,6 @@ class DictionarySerializer(Serializer):
|
|||||||
self.deserialize_task_spec(wf_spec, s_state, spec=spec)
|
self.deserialize_task_spec(wf_spec, s_state, spec=spec)
|
||||||
return spec
|
return spec
|
||||||
|
|
||||||
def serialize_loop_reset_task(self, spec):
|
|
||||||
s_state = self.serialize_task_spec(spec)
|
|
||||||
s_state['destination_id'] = spec.destination_id
|
|
||||||
s_state['destination_spec_name'] = spec.destination_spec_name
|
|
||||||
return s_state
|
|
||||||
|
|
||||||
def deserialize_loop_reset_task(self, wf_spec, s_state):
|
|
||||||
spec = LoopResetTask(wf_spec, s_state['name'], s_state['destination_id'],
|
|
||||||
s_state['destination_spec_name'])
|
|
||||||
self.deserialize_task_spec(wf_spec, s_state, spec=spec)
|
|
||||||
return spec
|
|
||||||
|
|
||||||
def serialize_join(self, spec):
|
def serialize_join(self, spec):
|
||||||
s_state = self.serialize_task_spec(spec)
|
s_state = self.serialize_task_spec(spec)
|
||||||
s_state['split_task'] = spec.split_task
|
s_state['split_task'] = spec.split_task
|
||||||
@ -555,8 +538,7 @@ class DictionarySerializer(Serializer):
|
|||||||
|
|
||||||
del spec.task_specs['Start']
|
del spec.task_specs['Start']
|
||||||
start_task_spec_state = s_state['task_specs']['Start']
|
start_task_spec_state = s_state['task_specs']['Start']
|
||||||
start_task_spec = StartTask.deserialize(
|
start_task_spec = StartTask.deserialize(self, spec, start_task_spec_state)
|
||||||
self, spec, start_task_spec_state)
|
|
||||||
spec.start = start_task_spec
|
spec.start = start_task_spec
|
||||||
spec.task_specs['Start'] = start_task_spec
|
spec.task_specs['Start'] = start_task_spec
|
||||||
for name, task_spec_state in list(s_state['task_specs'].items()):
|
for name, task_spec_state in list(s_state['task_specs'].items()):
|
||||||
@ -602,20 +584,34 @@ class DictionarySerializer(Serializer):
|
|||||||
s_state['wf_spec']"""
|
s_state['wf_spec']"""
|
||||||
|
|
||||||
if wf_spec is None:
|
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)
|
wf_spec = self.deserialize_workflow_spec(s_state['wf_spec'], **kwargs)
|
||||||
|
else:
|
||||||
|
reset_specs = []
|
||||||
|
|
||||||
workflow = wf_class(wf_spec)
|
workflow = wf_class(wf_spec)
|
||||||
workflow.data = self.deserialize_dict(s_state['data'])
|
workflow.data = self.deserialize_dict(s_state['data'])
|
||||||
workflow.success = s_state['success']
|
workflow.success = s_state['success']
|
||||||
workflow.spec = wf_spec
|
workflow.spec = wf_spec
|
||||||
workflow.task_tree = self.deserialize_task(
|
workflow.task_tree = self.deserialize_task(workflow, s_state['task_tree'], reset_specs)
|
||||||
workflow, s_state['task_tree'])
|
|
||||||
|
|
||||||
# Re-connect parents
|
# Re-connect parents
|
||||||
tasklist = list(workflow.get_tasks())
|
tasklist = workflow.get_tasks()
|
||||||
for task in tasklist:
|
for task in tasklist:
|
||||||
task.parent = workflow.get_task(task.parent,tasklist)
|
if task.parent is not None:
|
||||||
|
task.parent = workflow.get_task_from_id(task.parent, tasklist)
|
||||||
|
|
||||||
workflow.last_task = workflow.get_task(s_state['last_task'],tasklist)
|
if workflow.last_task is not None:
|
||||||
|
workflow.last_task = workflow.get_task_from_id(s_state['last_task'],tasklist)
|
||||||
workflow.update_task_mapping()
|
workflow.update_task_mapping()
|
||||||
|
|
||||||
return workflow
|
return workflow
|
||||||
@ -636,81 +632,48 @@ class DictionarySerializer(Serializer):
|
|||||||
" internal_data to store the subworkflow).")
|
" internal_data to store the subworkflow).")
|
||||||
|
|
||||||
s_state = dict()
|
s_state = dict()
|
||||||
|
|
||||||
# id
|
|
||||||
s_state['id'] = task.id
|
s_state['id'] = task.id
|
||||||
|
|
||||||
# workflow
|
|
||||||
s_state['workflow_name'] = task.workflow.name
|
s_state['workflow_name'] = task.workflow.name
|
||||||
|
|
||||||
# parent
|
|
||||||
s_state['parent'] = task.parent.id if task.parent is not None else None
|
s_state['parent'] = task.parent.id if task.parent is not None else None
|
||||||
|
|
||||||
# children
|
|
||||||
if not skip_children:
|
if not skip_children:
|
||||||
s_state['children'] = [
|
s_state['children'] = [
|
||||||
self.serialize_task(child) for child in task.children]
|
self.serialize_task(child) for child in task.children]
|
||||||
|
|
||||||
# state
|
|
||||||
s_state['state'] = task.state
|
s_state['state'] = task.state
|
||||||
s_state['triggered'] = task.triggered
|
s_state['triggered'] = task.triggered
|
||||||
|
|
||||||
# task_spec
|
|
||||||
s_state['task_spec'] = task.task_spec.name
|
s_state['task_spec'] = task.task_spec.name
|
||||||
|
|
||||||
# last_state_change
|
|
||||||
s_state['last_state_change'] = task.last_state_change
|
s_state['last_state_change'] = task.last_state_change
|
||||||
|
|
||||||
# data
|
|
||||||
s_state['data'] = self.serialize_dict(task.data)
|
s_state['data'] = self.serialize_dict(task.data)
|
||||||
|
|
||||||
# internal_data
|
|
||||||
s_state['internal_data'] = task.internal_data
|
s_state['internal_data'] = task.internal_data
|
||||||
|
|
||||||
return s_state
|
return s_state
|
||||||
|
|
||||||
|
def deserialize_task(self, workflow, s_state, ignored_specs=None):
|
||||||
def deserialize_task(self, workflow, s_state):
|
|
||||||
assert isinstance(workflow, Workflow)
|
assert isinstance(workflow, Workflow)
|
||||||
splits = s_state['task_spec'].split('_')
|
old_spec_name = s_state['task_spec']
|
||||||
oldtaskname = s_state['task_spec']
|
if old_spec_name in ignored_specs:
|
||||||
task_spec = workflow.get_task_spec_from_name(oldtaskname)
|
return None
|
||||||
|
task_spec = workflow.get_task_spec_from_name(old_spec_name)
|
||||||
if task_spec is None:
|
if task_spec is None:
|
||||||
raise MissingSpecError("Unknown task spec: " + oldtaskname)
|
raise MissingSpecError("Unknown task spec: " + old_spec_name)
|
||||||
task = Task(workflow, task_spec)
|
task = Task(workflow, task_spec)
|
||||||
|
|
||||||
if getattr(task_spec,'isSequential',False) and \
|
if getattr(task_spec,'isSequential',False) and s_state['internal_data'].get('splits') is not None:
|
||||||
s_state['internal_data'].get('splits') is not None:
|
|
||||||
task.task_spec.expanded = s_state['internal_data']['splits']
|
task.task_spec.expanded = s_state['internal_data']['splits']
|
||||||
|
|
||||||
|
|
||||||
# id
|
|
||||||
task.id = s_state['id']
|
task.id = s_state['id']
|
||||||
|
|
||||||
# parent
|
|
||||||
# as the task_tree might not be complete yet
|
# as the task_tree might not be complete yet
|
||||||
# keep the ids so they can be processed at the end
|
# keep the ids so they can be processed at the end
|
||||||
task.parent = s_state['parent']
|
task.parent = s_state['parent']
|
||||||
|
task.children = self._deserialize_task_children(task, s_state, ignored_specs)
|
||||||
# children
|
|
||||||
task.children = self._deserialize_task_children(task, s_state)
|
|
||||||
|
|
||||||
# state
|
|
||||||
task._state = s_state['state']
|
task._state = s_state['state']
|
||||||
task.triggered = s_state['triggered']
|
task.triggered = s_state['triggered']
|
||||||
|
|
||||||
# last_state_change
|
|
||||||
task.last_state_change = s_state['last_state_change']
|
task.last_state_change = s_state['last_state_change']
|
||||||
|
|
||||||
# data
|
|
||||||
task.data = self.deserialize_dict(s_state['data'])
|
task.data = self.deserialize_dict(s_state['data'])
|
||||||
|
|
||||||
# internal_data
|
|
||||||
task.internal_data = s_state['internal_data']
|
task.internal_data = s_state['internal_data']
|
||||||
return task
|
return task
|
||||||
|
|
||||||
def _deserialize_task_children(self, task, s_state):
|
def _deserialize_task_children(self, task, s_state, ignored_specs):
|
||||||
"""This may need to be overridden if you need to support
|
"""This may need to be overridden if you need to support
|
||||||
deserialization of sub-workflows"""
|
deserialization of sub-workflows"""
|
||||||
return [self.deserialize_task(task.workflow, c)
|
children = [self.deserialize_task(task.workflow, c, ignored_specs) for c in s_state['children']]
|
||||||
for c in s_state['children']]
|
return [c for c in children if c is not None]
|
@ -22,24 +22,20 @@ from .dict import DictionarySerializer
|
|||||||
class JSONSerializer(DictionarySerializer):
|
class JSONSerializer(DictionarySerializer):
|
||||||
|
|
||||||
def serialize_workflow_spec(self, wf_spec, **kwargs):
|
def serialize_workflow_spec(self, wf_spec, **kwargs):
|
||||||
thedict = super(JSONSerializer, self).serialize_workflow_spec(
|
thedict = super(JSONSerializer, self).serialize_workflow_spec(wf_spec, **kwargs)
|
||||||
wf_spec, **kwargs)
|
|
||||||
return self._dumps(thedict)
|
return self._dumps(thedict)
|
||||||
|
|
||||||
def deserialize_workflow_spec(self, s_state, **kwargs):
|
def deserialize_workflow_spec(self, s_state, **kwargs):
|
||||||
thedict = self._loads(s_state)
|
thedict = self._loads(s_state)
|
||||||
return super(JSONSerializer, self).deserialize_workflow_spec(
|
return super(JSONSerializer, self).deserialize_workflow_spec(thedict, **kwargs)
|
||||||
thedict, **kwargs)
|
|
||||||
|
|
||||||
def serialize_workflow(self, workflow, **kwargs):
|
def serialize_workflow(self, workflow, **kwargs):
|
||||||
thedict = super(JSONSerializer, self).serialize_workflow(
|
thedict = super(JSONSerializer, self).serialize_workflow(workflow, **kwargs)
|
||||||
workflow, **kwargs)
|
|
||||||
return self._dumps(thedict)
|
return self._dumps(thedict)
|
||||||
|
|
||||||
def deserialize_workflow(self, s_state, **kwargs):
|
def deserialize_workflow(self, s_state, **kwargs):
|
||||||
thedict = self._loads(s_state)
|
thedict = self._loads(s_state)
|
||||||
return super(JSONSerializer, self).deserialize_workflow(
|
return super(JSONSerializer, self).deserialize_workflow(thedict, **kwargs)
|
||||||
thedict, **kwargs)
|
|
||||||
|
|
||||||
def _object_hook(self, dct):
|
def _object_hook(self, dct):
|
||||||
if '__uuid__' in dct:
|
if '__uuid__' in dct:
|
||||||
|
@ -176,8 +176,7 @@ class XmlSerializer(Serializer):
|
|||||||
threshold_field = start_node.attrib.get('threshold-field', '').lower()
|
threshold_field = start_node.attrib.get('threshold-field', '').lower()
|
||||||
file_name = start_node.attrib.get('file', '').lower()
|
file_name = start_node.attrib.get('file', '').lower()
|
||||||
file_field = start_node.attrib.get('file-field', '').lower()
|
file_field = start_node.attrib.get('file-field', '').lower()
|
||||||
kwargs = {'lock': [],
|
kwargs = {'data': {},
|
||||||
'data': {},
|
|
||||||
'defines': {},
|
'defines': {},
|
||||||
'pre_assign': [],
|
'pre_assign': [],
|
||||||
'post_assign': []}
|
'post_assign': []}
|
||||||
@ -253,10 +252,6 @@ class XmlSerializer(Serializer):
|
|||||||
elif not isinstance(context, list):
|
elif not isinstance(context, list):
|
||||||
context = [context]
|
context = [context]
|
||||||
context.append(node.text)
|
context.append(node.text)
|
||||||
elif node.tag == 'lock':
|
|
||||||
if not node.text:
|
|
||||||
self.raise_parser_exception('Empty %s tag' % node.tag)
|
|
||||||
kwargs['lock'].append(node.text)
|
|
||||||
elif node.tag == 'pick':
|
elif node.tag == 'pick':
|
||||||
if not node.text:
|
if not node.text:
|
||||||
self.raise_parser_exception('Empty %s tag' % node.tag)
|
self.raise_parser_exception('Empty %s tag' % node.tag)
|
||||||
|
@ -15,15 +15,12 @@ from builtins import str
|
|||||||
# License along with this library; if not, write to the Free Software
|
# License along with this library; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
import re
|
|
||||||
import warnings
|
import warnings
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from lxml.etree import SubElement
|
from lxml.etree import SubElement
|
||||||
from ..workflow import Workflow
|
from ..workflow import Workflow
|
||||||
from .. import specs, operators
|
|
||||||
from ..task import Task, TaskStateNames
|
from ..task import Task, TaskStateNames
|
||||||
from ..operators import (Attrib, Assign, PathAttrib, Equal, NotEqual,
|
from ..operators import (Attrib, Assign, PathAttrib, Equal, NotEqual, GreaterThan, LessThan, Match)
|
||||||
GreaterThan, LessThan, Match)
|
|
||||||
from ..specs.AcquireMutex import AcquireMutex
|
from ..specs.AcquireMutex import AcquireMutex
|
||||||
from ..specs.Cancel import Cancel
|
from ..specs.Cancel import Cancel
|
||||||
from ..specs.CancelTask import CancelTask
|
from ..specs.CancelTask import CancelTask
|
||||||
@ -43,10 +40,8 @@ from ..specs.SubWorkflow import SubWorkflow
|
|||||||
from ..specs.ThreadStart import ThreadStart
|
from ..specs.ThreadStart import ThreadStart
|
||||||
from ..specs.ThreadMerge import ThreadMerge
|
from ..specs.ThreadMerge import ThreadMerge
|
||||||
from ..specs.ThreadSplit import ThreadSplit
|
from ..specs.ThreadSplit import ThreadSplit
|
||||||
from ..specs.Transform import Transform
|
|
||||||
from ..specs.Trigger import Trigger
|
from ..specs.Trigger import Trigger
|
||||||
from ..specs.WorkflowSpec import WorkflowSpec
|
from ..specs.WorkflowSpec import WorkflowSpec
|
||||||
from ..specs.LoopResetTask import LoopResetTask
|
|
||||||
from .base import Serializer, spec_map, op_map
|
from .base import Serializer, spec_map, op_map
|
||||||
from .exceptions import TaskNotSupportedError
|
from .exceptions import TaskNotSupportedError
|
||||||
|
|
||||||
@ -726,31 +721,17 @@ class XmlSerializer(Serializer):
|
|||||||
workflow.task_tree = self.deserialize_task(workflow, task_tree_elem[0])
|
workflow.task_tree = self.deserialize_task(workflow, task_tree_elem[0])
|
||||||
|
|
||||||
# Re-connect parents
|
# Re-connect parents
|
||||||
for task in workflow.get_tasks():
|
for task in workflow.get_tasks_iterator():
|
||||||
task.parent = workflow.get_task(task.parent)
|
if task.parent is not None:
|
||||||
|
task.parent = workflow.get_task_from_id(task.parent)
|
||||||
|
|
||||||
# last_task
|
# last_task
|
||||||
last_task = elem.findtext('last-task')
|
last_task = elem.findtext('last-task')
|
||||||
if last_task is not None:
|
if last_task is not None:
|
||||||
workflow.last_task = workflow.get_task(last_task)
|
workflow.last_task = workflow.get_task_from_id(last_task)
|
||||||
|
|
||||||
return workflow
|
return workflow
|
||||||
|
|
||||||
def serialize_loop_reset_task(self, spec):
|
|
||||||
elem = etree.Element('loop-reset-task')
|
|
||||||
SubElement(elem, 'destination_id').text = str(spec.destination_id)
|
|
||||||
SubElement(elem, 'destination_spec_name').text = str(spec.destination_spec_name)
|
|
||||||
return self.serialize_task_spec(spec, elem)
|
|
||||||
|
|
||||||
def deserialize_loop_reset_task(self, wf_spec, elem, cls=LoopResetTask, **kwargs):
|
|
||||||
destination_id = elem.findtext('destination_id')
|
|
||||||
destination_spec_name = elem.findtext('destination_spec_name')
|
|
||||||
|
|
||||||
task = self.deserialize_task_spec(wf_spec, elem, cls,
|
|
||||||
destination_id=destination_id,
|
|
||||||
destination_spec_name=destination_spec_name)
|
|
||||||
return task
|
|
||||||
|
|
||||||
def serialize_task(self, task, skip_children=False):
|
def serialize_task(self, task, skip_children=False):
|
||||||
assert isinstance(task, Task)
|
assert isinstance(task, Task)
|
||||||
|
|
||||||
|
@ -55,9 +55,9 @@ class Cancel(TaskSpec):
|
|||||||
if len(self.outputs) > 0:
|
if len(self.outputs) > 0:
|
||||||
raise WorkflowException('Cancel with an output.', task_spec=self)
|
raise WorkflowException('Cancel with an output.', task_spec=self)
|
||||||
|
|
||||||
def _on_complete_hook(self, my_task):
|
def _run_hook(self, my_task):
|
||||||
my_task.workflow.cancel(self.cancel_successfully)
|
my_task.workflow.cancel(self.cancel_successfully)
|
||||||
TaskSpec._on_complete_hook(self, my_task)
|
return True
|
||||||
|
|
||||||
def serialize(self, serializer):
|
def serialize(self, serializer):
|
||||||
return serializer.serialize_cancel(self)
|
return serializer.serialize_cancel(self)
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
# License along with this library; if not, write to the Free Software
|
# License along with this library; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
from .base import TaskSpec
|
|
||||||
from .Trigger import Trigger
|
from .Trigger import Trigger
|
||||||
|
|
||||||
|
|
||||||
@ -30,12 +29,12 @@ class CancelTask(Trigger):
|
|||||||
parallel split.
|
parallel split.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _on_complete_hook(self, my_task):
|
def _run_hook(self, my_task):
|
||||||
for task_name in self.context:
|
for task_name in self.context:
|
||||||
cancel_tasks = my_task.workflow.get_task_spec_from_name(task_name)
|
cancel_tasks = my_task.workflow.get_task_spec_from_name(task_name)
|
||||||
for cancel_task in my_task._get_root()._find_any(cancel_tasks):
|
for cancel_task in my_task._get_root()._find_any(cancel_tasks):
|
||||||
cancel_task.cancel()
|
cancel_task.cancel()
|
||||||
TaskSpec._on_complete_hook(self, my_task)
|
return True
|
||||||
|
|
||||||
def serialize(self, serializer):
|
def serialize(self, serializer):
|
||||||
return serializer.serialize_cancel_task(self)
|
return serializer.serialize_cancel_task(self)
|
||||||
|
@ -117,7 +117,7 @@ class Celery(TaskSpec):
|
|||||||
self.call = call or []
|
self.call = call or []
|
||||||
self.args = call_args or {}
|
self.args = call_args or {}
|
||||||
self.merge_results = merge_results
|
self.merge_results = merge_results
|
||||||
skip = 'data', 'defines', 'pre_assign', 'post_assign', 'lock'
|
skip = 'data', 'defines', 'pre_assign', 'post_assign'
|
||||||
self.kwargs = dict(i for i in list(kwargs.items()) if i[0] not in skip)
|
self.kwargs = dict(i for i in list(kwargs.items()) if i[0] not in skip)
|
||||||
self.result_key = result_key
|
self.result_key = result_key
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ class Choose(Trigger):
|
|||||||
self.context = context
|
self.context = context
|
||||||
self.choice = choice is not None and choice or []
|
self.choice = choice is not None and choice or []
|
||||||
|
|
||||||
def _on_complete_hook(self, my_task):
|
def _run_hook(self, my_task):
|
||||||
context = my_task.workflow.get_task_spec_from_name(self.context)
|
context = my_task.workflow.get_task_spec_from_name(self.context)
|
||||||
triggered = []
|
triggered = []
|
||||||
for task in my_task.workflow.task_tree:
|
for task in my_task.workflow.task_tree:
|
||||||
@ -66,7 +66,7 @@ class Choose(Trigger):
|
|||||||
triggered.append(task)
|
triggered.append(task)
|
||||||
for task in triggered:
|
for task in triggered:
|
||||||
context._predict(task)
|
context._predict(task)
|
||||||
TaskSpec._on_complete_hook(self, my_task)
|
return True
|
||||||
|
|
||||||
def serialize(self, serializer):
|
def serialize(self, serializer):
|
||||||
return serializer.serialize_choose(self)
|
return serializer.serialize_choose(self)
|
||||||
|
@ -61,16 +61,7 @@ class ExclusiveChoice(MultiChoice):
|
|||||||
if self.default_task_spec is None:
|
if self.default_task_spec is None:
|
||||||
raise WorkflowException('A default output is required.', task_spec=self)
|
raise WorkflowException('A default output is required.', task_spec=self)
|
||||||
|
|
||||||
def _predict_hook(self, my_task):
|
def _run_hook(self, my_task):
|
||||||
# If the task's status is not predicted, we default to MAYBE
|
|
||||||
# for all it's outputs except the default choice, which is
|
|
||||||
# LIKELY.
|
|
||||||
# Otherwise, copy my own state to the children.
|
|
||||||
my_task._sync_children(self.outputs)
|
|
||||||
spec = self._wf_spec.get_task_spec_from_name(self.default_task_spec)
|
|
||||||
my_task._set_likely_task(spec)
|
|
||||||
|
|
||||||
def _on_complete_hook(self, my_task):
|
|
||||||
|
|
||||||
output = self._wf_spec.get_task_spec_from_name(self.default_task_spec)
|
output = self._wf_spec.get_task_spec_from_name(self.default_task_spec)
|
||||||
for condition, spec_name in self.cond_task_specs:
|
for condition, spec_name in self.cond_task_specs:
|
||||||
@ -82,6 +73,10 @@ class ExclusiveChoice(MultiChoice):
|
|||||||
raise WorkflowException(f'No conditions satisfied for {my_task.task_spec.name}', task_spec=self)
|
raise WorkflowException(f'No conditions satisfied for {my_task.task_spec.name}', task_spec=self)
|
||||||
|
|
||||||
my_task._sync_children([output], TaskState.FUTURE)
|
my_task._sync_children([output], TaskState.FUTURE)
|
||||||
|
for child in my_task.children:
|
||||||
|
child.task_spec._predict(child, mask=TaskState.FUTURE|TaskState.PREDICTED_MASK)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def serialize(self, serializer):
|
def serialize(self, serializer):
|
||||||
return serializer.serialize_exclusive_choice(self)
|
return serializer.serialize_exclusive_choice(self)
|
||||||
|
@ -120,8 +120,7 @@ class Join(TaskSpec):
|
|||||||
# If the task is predicted with less outputs than he has
|
# If the task is predicted with less outputs than he has
|
||||||
# children, that means the prediction may be incomplete (for
|
# children, that means the prediction may be incomplete (for
|
||||||
# example, because a prediction is not yet possible at this time).
|
# example, because a prediction is not yet possible at this time).
|
||||||
if not child._is_definite() \
|
if child._is_predicted() and len(child.task_spec.outputs) > len(child.children):
|
||||||
and len(child.task_spec.outputs) > len(child.children):
|
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -153,8 +152,8 @@ class Join(TaskSpec):
|
|||||||
|
|
||||||
# Look at the tree to find all places where this task is used.
|
# Look at the tree to find all places where this task is used.
|
||||||
tasks = []
|
tasks = []
|
||||||
for input in self.inputs:
|
for spec in self.inputs:
|
||||||
tasks += my_task.workflow.task_mapping[my_task.thread_id][input]
|
tasks.extend([ t for t in my_task.workflow.task_tree._find_any(spec) if t.thread_id == my_task.thread_id ])
|
||||||
|
|
||||||
# Look up which tasks have already completed.
|
# Look up which tasks have already completed.
|
||||||
waiting_tasks = []
|
waiting_tasks = []
|
||||||
@ -162,7 +161,7 @@ class Join(TaskSpec):
|
|||||||
for task in tasks:
|
for task in tasks:
|
||||||
if task.parent is None or task._has_state(TaskState.COMPLETED):
|
if task.parent is None or task._has_state(TaskState.COMPLETED):
|
||||||
completed += 1
|
completed += 1
|
||||||
else:
|
elif not task._is_finished():
|
||||||
waiting_tasks.append(task)
|
waiting_tasks.append(task)
|
||||||
|
|
||||||
# If the threshold was reached, get ready to fire.
|
# If the threshold was reached, get ready to fire.
|
||||||
@ -186,8 +185,6 @@ class Join(TaskSpec):
|
|||||||
waiting_tasks = []
|
waiting_tasks = []
|
||||||
completed = 0
|
completed = 0
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
# Refresh path prediction.
|
|
||||||
task.task_spec._predict(task)
|
|
||||||
if not self._branch_may_merge_at(task):
|
if not self._branch_may_merge_at(task):
|
||||||
completed += 1
|
completed += 1
|
||||||
elif self._branch_is_complete(task):
|
elif self._branch_is_complete(task):
|
||||||
@ -204,16 +201,16 @@ class Join(TaskSpec):
|
|||||||
Returns True if the threshold was reached, False otherwise.
|
Returns True if the threshold was reached, False otherwise.
|
||||||
Also returns the list of tasks that yet need to be completed.
|
Also returns the list of tasks that yet need to be completed.
|
||||||
"""
|
"""
|
||||||
# If the threshold was already reached, there is nothing else to do.
|
if my_task._is_finished():
|
||||||
if my_task._has_state(TaskState.COMPLETED):
|
return False, None
|
||||||
return True, None
|
|
||||||
if my_task._has_state(TaskState.READY):
|
if my_task._has_state(TaskState.READY):
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
# Check whether we may fire.
|
# Check whether we may fire.
|
||||||
if self.split_task is None:
|
if self.split_task is None:
|
||||||
return self._check_threshold_unstructured(my_task, force)
|
return self._check_threshold_unstructured(my_task, force)
|
||||||
return self._check_threshold_structured(my_task, force)
|
else:
|
||||||
|
return self._check_threshold_structured(my_task, force)
|
||||||
|
|
||||||
def _update_hook(self, my_task):
|
def _update_hook(self, my_task):
|
||||||
# Check whether enough incoming branches have completed.
|
# Check whether enough incoming branches have completed.
|
||||||
@ -224,22 +221,16 @@ class Join(TaskSpec):
|
|||||||
if self.cancel_remaining:
|
if self.cancel_remaining:
|
||||||
for task in waiting_tasks:
|
for task in waiting_tasks:
|
||||||
task.cancel()
|
task.cancel()
|
||||||
|
|
||||||
# Update the state of our child objects.
|
# Update the state of our child objects.
|
||||||
self._do_join(my_task)
|
self._do_join(my_task)
|
||||||
else:
|
return True
|
||||||
|
elif not my_task._is_finished():
|
||||||
my_task._set_state(TaskState.WAITING)
|
my_task._set_state(TaskState.WAITING)
|
||||||
|
|
||||||
def _do_join(self, my_task):
|
def _find_tasks(self, my_task):
|
||||||
|
|
||||||
split_task = self._get_split_task(my_task)
|
split_task = self._get_split_task(my_task)
|
||||||
|
|
||||||
# Identify all corresponding task instances within the thread.
|
# Identify all corresponding task instances within the thread.
|
||||||
# Also remember which of those instances was most recently changed,
|
|
||||||
# because we are making this one the instance that will
|
|
||||||
# continue the thread of control. In other words, we will continue
|
|
||||||
# to build the task tree underneath the most recently changed task.
|
|
||||||
last_changed = None
|
|
||||||
thread_tasks = []
|
thread_tasks = []
|
||||||
for task in split_task._find_any(self):
|
for task in split_task._find_any(self):
|
||||||
# Ignore tasks from other threads.
|
# Ignore tasks from other threads.
|
||||||
@ -248,27 +239,16 @@ class Join(TaskSpec):
|
|||||||
# Ignore my outgoing branches.
|
# Ignore my outgoing branches.
|
||||||
if self.split_task and task._is_descendant_of(my_task):
|
if self.split_task and task._is_descendant_of(my_task):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# We have found a matching instance.
|
# We have found a matching instance.
|
||||||
thread_tasks.append(task)
|
thread_tasks.append(task)
|
||||||
|
return thread_tasks
|
||||||
|
|
||||||
# Check whether the state of the instance was recently
|
def _do_join(self, my_task):
|
||||||
# changed.
|
|
||||||
changed = task.parent.last_state_change
|
|
||||||
if last_changed is None or changed > last_changed.parent.last_state_change:
|
|
||||||
last_changed = task
|
|
||||||
|
|
||||||
# Mark the identified task instances as COMPLETED. The exception
|
# Execution will continue from this task; mark others as cancelled
|
||||||
# is the most recently changed task, for which we assume READY.
|
for task in self._find_tasks(my_task):
|
||||||
# By setting the state to READY only, we allow for calling
|
if task != my_task:
|
||||||
# :class:`Task.complete()`, which leads to the task tree being
|
task._set_state(TaskState.CANCELLED)
|
||||||
# (re)built underneath the node.
|
|
||||||
for task in thread_tasks:
|
|
||||||
if task == last_changed:
|
|
||||||
self.entered_event.emit(my_task.workflow, my_task)
|
|
||||||
task._ready()
|
|
||||||
else:
|
|
||||||
task._set_state(TaskState.COMPLETED)
|
|
||||||
task._drop_children()
|
task._drop_children()
|
||||||
|
|
||||||
def _on_trigger(self, my_task):
|
def _on_trigger(self, my_task):
|
||||||
@ -276,10 +256,11 @@ class Join(TaskSpec):
|
|||||||
May be called to fire the Join before the incoming branches are
|
May be called to fire the Join before the incoming branches are
|
||||||
completed.
|
completed.
|
||||||
"""
|
"""
|
||||||
for task in my_task.workflow.task_tree._find_any(self):
|
tasks = sorted(self._find_tasks(my_task), key=lambda t: t.last_state_change)
|
||||||
if task.thread_id != my_task.thread_id:
|
for task in tasks[:-1]:
|
||||||
continue
|
task._set_state(TaskState.CANCELLED)
|
||||||
self._do_join(task)
|
task._drop_children()
|
||||||
|
tasks[-1]._ready()
|
||||||
|
|
||||||
def serialize(self, serializer):
|
def serialize(self, serializer):
|
||||||
return serializer.serialize_join(self)
|
return serializer.serialize_join(self)
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (C) 2021 Sartography
|
|
||||||
#
|
|
||||||
# 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 .base import TaskSpec
|
|
||||||
from ..task import TaskState
|
|
||||||
from SpiffWorkflow.exceptions import WorkflowTaskException
|
|
||||||
|
|
||||||
|
|
||||||
class LoopResetTask(TaskSpec):
|
|
||||||
|
|
||||||
"""
|
|
||||||
This task is used as a placeholder when we are going to loopback
|
|
||||||
to a previous point in the workflow. When this task is completed,
|
|
||||||
it will reset the workflow back to a previous point.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, wf_spec, name, destination_id, destination_spec_name, **kwargs):
|
|
||||||
"""
|
|
||||||
Constructor.
|
|
||||||
|
|
||||||
:param script: the script that must be executed by the script engine.
|
|
||||||
"""
|
|
||||||
super(LoopResetTask, self).__init__(wf_spec, name, **kwargs)
|
|
||||||
self.destination_id = destination_id
|
|
||||||
self.destination_spec_name = destination_spec_name
|
|
||||||
|
|
||||||
def _on_complete_hook(self, task):
|
|
||||||
try:
|
|
||||||
# Prefer the exact task id, but if not available, use the
|
|
||||||
# last instance of the task_spec.
|
|
||||||
destination = task.workflow.get_task(self.destination_id)
|
|
||||||
if not destination:
|
|
||||||
destination = task.workflow.get_tasks_from_spec_name(
|
|
||||||
self.destination_spec_name)[-1]
|
|
||||||
|
|
||||||
destination.reset_token(task.data, reset_data=False)
|
|
||||||
except Exception as e:
|
|
||||||
# set state to WAITING (because it is definitely not COMPLETED)
|
|
||||||
# and raise WorkflowException pointing to this task because
|
|
||||||
# maybe upstream someone will be able to handle this situation
|
|
||||||
task._set_state(TaskState.WAITING)
|
|
||||||
if isinstance(e, WorkflowTaskException):
|
|
||||||
e.add_note('Error occurred during a loop back to a previous step.')
|
|
||||||
raise e
|
|
||||||
else:
|
|
||||||
raise WorkflowTaskException(
|
|
||||||
'Error during loop back:' + str(e), task=task, exception=e)
|
|
||||||
super(LoopResetTask, self)._on_complete_hook(task)
|
|
||||||
|
|
||||||
def serialize(self, serializer):
|
|
||||||
return serializer.serialize_loop_reset_task(self)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def deserialize(cls, serializer, wf_spec, s_state):
|
|
||||||
return serializer.deserialize_loop_reset_task(wf_spec, s_state)
|
|
||||||
|
|
@ -89,32 +89,18 @@ class MultiChoice(TaskSpec):
|
|||||||
# The caller needs to make sure that predict() is called.
|
# The caller needs to make sure that predict() is called.
|
||||||
|
|
||||||
def _predict_hook(self, my_task):
|
def _predict_hook(self, my_task):
|
||||||
if self.choice:
|
conditional, unconditional = [], []
|
||||||
outputs = [self._wf_spec.get_task_spec_from_name(o)
|
|
||||||
for o in self.choice]
|
|
||||||
else:
|
|
||||||
outputs = self.outputs
|
|
||||||
|
|
||||||
# Default to MAYBE for all conditional outputs, default to LIKELY
|
|
||||||
# for unconditional ones. We can not default to FUTURE, because
|
|
||||||
# a call to trigger() may override the unconditional paths.
|
|
||||||
my_task._sync_children(outputs)
|
|
||||||
if not my_task._is_definite():
|
|
||||||
best_state = my_task.state
|
|
||||||
else:
|
|
||||||
best_state = TaskState.LIKELY
|
|
||||||
|
|
||||||
# Collect a list of all unconditional outputs.
|
|
||||||
outputs = []
|
|
||||||
for condition, output in self.cond_task_specs:
|
for condition, output in self.cond_task_specs:
|
||||||
if condition is None:
|
if self.choice is not None and output not in self.choice:
|
||||||
outputs.append(self._wf_spec.get_task_spec_from_name(output))
|
|
||||||
|
|
||||||
for child in my_task.children:
|
|
||||||
if child._is_definite():
|
|
||||||
continue
|
continue
|
||||||
if child.task_spec in outputs:
|
if condition is None:
|
||||||
child._set_state(best_state)
|
unconditional.append(self._wf_spec.get_task_spec_from_name(output))
|
||||||
|
else:
|
||||||
|
conditional.append(self._wf_spec.get_task_spec_from_name(output))
|
||||||
|
state = TaskState.MAYBE if my_task.state == TaskState.MAYBE else TaskState.LIKELY
|
||||||
|
my_task._sync_children(unconditional, state)
|
||||||
|
for spec in conditional:
|
||||||
|
my_task._add_child(spec, TaskState.MAYBE)
|
||||||
|
|
||||||
def _get_matching_outputs(self, my_task):
|
def _get_matching_outputs(self, my_task):
|
||||||
outputs = []
|
outputs = []
|
||||||
@ -125,12 +111,12 @@ class MultiChoice(TaskSpec):
|
|||||||
outputs.append(self._wf_spec.get_task_spec_from_name(output))
|
outputs.append(self._wf_spec.get_task_spec_from_name(output))
|
||||||
return outputs
|
return outputs
|
||||||
|
|
||||||
def _on_complete_hook(self, my_task):
|
def _run_hook(self, my_task):
|
||||||
"""
|
"""Runs the task. Should not be called directly."""
|
||||||
Runs the task. Should not be called directly.
|
|
||||||
Returns True if completed, False otherwise.
|
|
||||||
"""
|
|
||||||
my_task._sync_children(self._get_matching_outputs(my_task), TaskState.FUTURE)
|
my_task._sync_children(self._get_matching_outputs(my_task), TaskState.FUTURE)
|
||||||
|
for child in my_task.children:
|
||||||
|
child.task_spec._predict(child, mask=TaskState.FUTURE|TaskState.PREDICTED_MASK)
|
||||||
|
return True
|
||||||
|
|
||||||
def serialize(self, serializer):
|
def serialize(self, serializer):
|
||||||
return serializer.serialize_multi_choice(self)
|
return serializer.serialize_multi_choice(self)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from builtins import range
|
|
||||||
# Copyright (C) 2007 Samuel Abels
|
# Copyright (C) 2007 Samuel Abels
|
||||||
#
|
#
|
||||||
# This library is free software; you can redistribute it and/or
|
# This library is free software; you can redistribute it and/or
|
||||||
@ -75,33 +74,24 @@ class MultiInstance(TaskSpec):
|
|||||||
for output in self.outputs:
|
for output in self.outputs:
|
||||||
new_task = my_task._add_child(output, state)
|
new_task = my_task._add_child(output, state)
|
||||||
new_task.triggered = True
|
new_task.triggered = True
|
||||||
output._predict(new_task)
|
output._predict(new_task, mask=TaskState.FUTURE|TaskState.READY|TaskState.PREDICTED_MASK)
|
||||||
|
|
||||||
def _get_predicted_outputs(self, my_task):
|
def _get_predicted_outputs(self, my_task):
|
||||||
split_n = int(valueof(my_task, self.times, 1))
|
split_n = int(valueof(my_task, self.times, 1))
|
||||||
|
return self.outputs * split_n
|
||||||
# Predict the outputs.
|
|
||||||
outputs = []
|
|
||||||
for i in range(split_n):
|
|
||||||
outputs += self.outputs
|
|
||||||
return outputs
|
|
||||||
|
|
||||||
def _predict_hook(self, my_task):
|
def _predict_hook(self, my_task):
|
||||||
split_n = int(valueof(my_task, self.times, 1))
|
outputs = self._get_predicted_outputs(my_task)
|
||||||
my_task._set_internal_data(splits=split_n)
|
|
||||||
|
|
||||||
# Create the outgoing tasks.
|
|
||||||
outputs = []
|
|
||||||
for i in range(split_n):
|
|
||||||
outputs += self.outputs
|
|
||||||
if my_task._is_definite():
|
if my_task._is_definite():
|
||||||
my_task._sync_children(outputs, TaskState.FUTURE)
|
my_task._sync_children(outputs, TaskState.FUTURE)
|
||||||
else:
|
else:
|
||||||
my_task._sync_children(outputs, TaskState.LIKELY)
|
my_task._sync_children(outputs, TaskState.LIKELY)
|
||||||
|
|
||||||
def _on_complete_hook(self, my_task):
|
def _run_hook(self, my_task):
|
||||||
outputs = self._get_predicted_outputs(my_task)
|
outputs = self._get_predicted_outputs(my_task)
|
||||||
my_task._sync_children(outputs, TaskState.FUTURE)
|
my_task._sync_children(outputs, TaskState.FUTURE)
|
||||||
|
self._predict(my_task, mask=TaskState.FUTURE|TaskState.PREDICTED_MASK)
|
||||||
|
return True
|
||||||
|
|
||||||
def serialize(self, serializer):
|
def serialize(self, serializer):
|
||||||
return serializer.serialize_multi_instance(self)
|
return serializer.serialize_multi_instance(self)
|
||||||
|
@ -47,10 +47,10 @@ class ReleaseMutex(TaskSpec):
|
|||||||
TaskSpec.__init__(self, wf_spec, name, **kwargs)
|
TaskSpec.__init__(self, wf_spec, name, **kwargs)
|
||||||
self.mutex = mutex
|
self.mutex = mutex
|
||||||
|
|
||||||
def _on_complete_hook(self, my_task):
|
def _run_hook(self, my_task):
|
||||||
mutex = my_task.workflow._get_mutex(self.mutex)
|
mutex = my_task.workflow._get_mutex(self.mutex)
|
||||||
mutex.unlock()
|
mutex.unlock()
|
||||||
TaskSpec._on_complete_hook(self, my_task)
|
return True
|
||||||
|
|
||||||
def serialize(self, serializer):
|
def serialize(self, serializer):
|
||||||
return serializer.serialize_release_mutex(self)
|
return serializer.serialize_release_mutex(self)
|
||||||
|
@ -98,41 +98,31 @@ class SubWorkflow(TaskSpec):
|
|||||||
xml = etree.parse(fp).getroot()
|
xml = etree.parse(fp).getroot()
|
||||||
wf_spec = WorkflowSpec.deserialize(serializer, xml, filename=file_name)
|
wf_spec = WorkflowSpec.deserialize(serializer, xml, filename=file_name)
|
||||||
outer_workflow = my_task.workflow.outer_workflow
|
outer_workflow = my_task.workflow.outer_workflow
|
||||||
return Workflow(wf_spec, parent=outer_workflow)
|
subworkflow = Workflow(wf_spec, parent=outer_workflow)
|
||||||
|
my_task._sync_children(self.outputs, TaskState.FUTURE)
|
||||||
def _on_ready_before_hook(self, my_task):
|
|
||||||
subworkflow = self._create_subworkflow(my_task)
|
|
||||||
subworkflow.completed_event.connect(
|
|
||||||
self._on_subworkflow_completed, my_task)
|
|
||||||
self._integrate_subworkflow_tree(my_task, subworkflow)
|
|
||||||
my_task._set_internal_data(subworkflow=subworkflow)
|
|
||||||
|
|
||||||
def _integrate_subworkflow_tree(self, my_task, subworkflow):
|
|
||||||
# Integrate the tree of the subworkflow into the tree of this workflow.
|
|
||||||
my_task._sync_children(self.outputs, TaskState.LIKELY)
|
|
||||||
for child in subworkflow.task_tree.children:
|
for child in subworkflow.task_tree.children:
|
||||||
my_task.children.insert(0, child)
|
my_task.children.insert(0, child)
|
||||||
child.parent = my_task
|
child.parent = my_task
|
||||||
|
child.state = TaskState.READY
|
||||||
|
subworkflow.completed_event.connect(self._on_subworkflow_completed, my_task)
|
||||||
|
my_task._set_internal_data(subworkflow=subworkflow)
|
||||||
|
my_task._set_state(TaskState.WAITING)
|
||||||
|
|
||||||
def _on_ready_hook(self, my_task):
|
def _run_hook(self, my_task):
|
||||||
# Assign variables, if so requested.
|
# Assign variables, if so requested.
|
||||||
subworkflow = my_task._get_internal_data('subworkflow')
|
subworkflow = my_task._get_internal_data('subworkflow')
|
||||||
for child in subworkflow.task_tree.children:
|
for child in subworkflow.task_tree.children:
|
||||||
for assignment in self.in_assign:
|
for assignment in self.in_assign:
|
||||||
assignment.assign(my_task, child)
|
assignment.assign(my_task, child)
|
||||||
child.task_spec._update(child)
|
child.task_spec._update(child)
|
||||||
# Instead of completing immediately, we'll wait for the subworkflow to complete
|
return True
|
||||||
my_task._set_state(TaskState.WAITING)
|
|
||||||
|
|
||||||
def _update_hook(self, my_task):
|
def _update_hook(self, my_task):
|
||||||
|
|
||||||
super()._update_hook(my_task)
|
super()._update_hook(my_task)
|
||||||
subworkflow = my_task._get_internal_data('subworkflow')
|
subworkflow = my_task._get_internal_data('subworkflow')
|
||||||
if subworkflow is None:
|
if subworkflow is None:
|
||||||
# On the first update, we have to create the subworkflow
|
self._create_subworkflow(my_task)
|
||||||
return True
|
|
||||||
elif subworkflow.is_completed():
|
elif subworkflow.is_completed():
|
||||||
# Then wait until it finishes to complete
|
|
||||||
my_task.complete()
|
my_task.complete()
|
||||||
|
|
||||||
def _on_subworkflow_completed(self, subworkflow, my_task):
|
def _on_subworkflow_completed(self, subworkflow, my_task):
|
||||||
|
@ -62,8 +62,7 @@ class ThreadMerge(Join):
|
|||||||
# task that did the conditional parallel split.
|
# task that did the conditional parallel split.
|
||||||
split_task = my_task._find_ancestor_from_name(self.split_task)
|
split_task = my_task._find_ancestor_from_name(self.split_task)
|
||||||
if split_task is None:
|
if split_task is None:
|
||||||
msg = 'Join with %s, which was not reached' % self.split_task
|
raise WorkflowException(f'Join with %s, which was not reached {self.split_task}', task_spec=self)
|
||||||
raise WorkflowException(msg, task_spec=self)
|
|
||||||
tasks = split_task.task_spec._get_activated_threads(split_task)
|
tasks = split_task.task_spec._get_activated_threads(split_task)
|
||||||
|
|
||||||
# The default threshold is the number of threads that were started.
|
# The default threshold is the number of threads that were started.
|
||||||
@ -105,8 +104,7 @@ class ThreadMerge(Join):
|
|||||||
my_task._set_state(TaskState.WAITING)
|
my_task._set_state(TaskState.WAITING)
|
||||||
return
|
return
|
||||||
|
|
||||||
split_task_spec = my_task.workflow.get_task_spec_from_name(
|
split_task_spec = my_task.workflow.get_task_spec_from_name(self.split_task)
|
||||||
self.split_task)
|
|
||||||
split_task = my_task._find_ancestor(split_task_spec)
|
split_task = my_task._find_ancestor(split_task_spec)
|
||||||
|
|
||||||
# Find the inbound task that was completed last.
|
# Find the inbound task that was completed last.
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from builtins import range
|
|
||||||
# Copyright (C) 2007 Samuel Abels
|
# Copyright (C) 2007 Samuel Abels
|
||||||
#
|
#
|
||||||
# This library is free software; you can redistribute it and/or
|
# This library is free software; you can redistribute it and/or
|
||||||
@ -108,31 +107,25 @@ class ThreadSplit(TaskSpec):
|
|||||||
new_task = my_task.add_child(output, TaskState.READY)
|
new_task = my_task.add_child(output, TaskState.READY)
|
||||||
new_task.triggered = True
|
new_task.triggered = True
|
||||||
|
|
||||||
def _predict_hook(self, my_task):
|
def _get_predicted_outputs(self, my_task):
|
||||||
split_n = int(valueof(my_task, self.times))
|
split_n = int(valueof(my_task, self.times))
|
||||||
|
return [self.thread_starter] * split_n
|
||||||
|
|
||||||
|
def _predict_hook(self, my_task):
|
||||||
# if we were created with thread_starter suppressed, connect it now.
|
# if we were created with thread_starter suppressed, connect it now.
|
||||||
if self.thread_starter is None:
|
if self.thread_starter is None:
|
||||||
self.thread_starter = self.outputs[0]
|
self.thread_starter = self.outputs[0]
|
||||||
|
|
||||||
# Predict the outputs.
|
outputs = self._get_predicted_outputs(my_task)
|
||||||
outputs = []
|
|
||||||
for i in range(split_n):
|
|
||||||
outputs.append(self.thread_starter)
|
|
||||||
if my_task._is_definite():
|
if my_task._is_definite():
|
||||||
my_task._sync_children(outputs, TaskState.FUTURE)
|
my_task._sync_children(outputs, TaskState.FUTURE)
|
||||||
else:
|
else:
|
||||||
my_task._sync_children(outputs, TaskState.LIKELY)
|
my_task._sync_children(outputs, TaskState.LIKELY)
|
||||||
|
|
||||||
def _on_complete_hook(self, my_task):
|
def _run_hook(self, my_task):
|
||||||
# Split, and remember the number of splits in the context data.
|
outputs = self._get_predicted_outputs(my_task)
|
||||||
split_n = int(valueof(my_task, self.times))
|
|
||||||
|
|
||||||
# Create the outgoing tasks.
|
|
||||||
outputs = []
|
|
||||||
for i in range(split_n):
|
|
||||||
outputs.append(self.thread_starter)
|
|
||||||
my_task._sync_children(outputs, TaskState.FUTURE)
|
my_task._sync_children(outputs, TaskState.FUTURE)
|
||||||
|
return True
|
||||||
|
|
||||||
def serialize(self, serializer):
|
def serialize(self, serializer):
|
||||||
return serializer.serialize_thread_split(self)
|
return serializer.serialize_thread_split(self)
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
from .base import TaskSpec
|
from .base import TaskSpec
|
||||||
|
from SpiffWorkflow.task import TaskState
|
||||||
|
|
||||||
|
|
||||||
class ThreadStart(TaskSpec):
|
class ThreadStart(TaskSpec):
|
||||||
@ -42,9 +43,10 @@ class ThreadStart(TaskSpec):
|
|||||||
TaskSpec.__init__(self, wf_spec, name, **kwargs)
|
TaskSpec.__init__(self, wf_spec, name, **kwargs)
|
||||||
self.internal = True
|
self.internal = True
|
||||||
|
|
||||||
def _on_complete_hook(self, my_task):
|
def _run_hook(self, my_task):
|
||||||
my_task._assign_new_thread_id()
|
my_task._assign_new_thread_id()
|
||||||
TaskSpec._on_complete_hook(self, my_task)
|
my_task._sync_children(self.outputs, TaskState.READY)
|
||||||
|
return True
|
||||||
|
|
||||||
def serialize(self, serializer):
|
def serialize(self, serializer):
|
||||||
return serializer.serialize_thread_start(self)
|
return serializer.serialize_thread_start(self)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from builtins import range
|
|
||||||
# Copyright (C) 2007 Samuel Abels
|
# Copyright (C) 2007 Samuel Abels
|
||||||
#
|
#
|
||||||
# This library is free software; you can redistribute it and/or
|
# This library is free software; you can redistribute it and/or
|
||||||
@ -65,15 +64,14 @@ class Trigger(TaskSpec):
|
|||||||
self.queued += 1
|
self.queued += 1
|
||||||
# All tasks that have already completed need to be put back to
|
# All tasks that have already completed need to be put back to
|
||||||
# READY.
|
# READY.
|
||||||
for thetask in my_task.workflow.task_tree:
|
for task in my_task.workflow.task_tree:
|
||||||
if thetask.thread_id != my_task.thread_id:
|
if task.thread_id != my_task.thread_id:
|
||||||
continue
|
continue
|
||||||
if (thetask.task_spec == self and
|
if task.task_spec == self and task._has_state(TaskState.COMPLETED):
|
||||||
thetask._has_state(TaskState.COMPLETED)):
|
task._set_state(TaskState.FUTURE)
|
||||||
thetask._set_state(TaskState.FUTURE)
|
task._ready()
|
||||||
thetask._ready()
|
|
||||||
|
|
||||||
def _on_complete_hook(self, my_task):
|
def _run_hook(self, my_task):
|
||||||
"""
|
"""
|
||||||
A hook into _on_complete() that does the task specific work.
|
A hook into _on_complete() that does the task specific work.
|
||||||
|
|
||||||
@ -85,10 +83,10 @@ class Trigger(TaskSpec):
|
|||||||
times = int(valueof(my_task, self.times, 1)) + self.queued
|
times = int(valueof(my_task, self.times, 1)) + self.queued
|
||||||
for i in range(times):
|
for i in range(times):
|
||||||
for task_name in self.context:
|
for task_name in self.context:
|
||||||
task = my_task.workflow.get_task_spec_from_name(task_name)
|
task_spec = my_task.workflow.get_task_spec_from_name(task_name)
|
||||||
task._on_trigger(my_task)
|
task_spec._on_trigger(my_task)
|
||||||
self.queued = 0
|
self.queued = 0
|
||||||
TaskSpec._on_complete_hook(self, my_task)
|
return True
|
||||||
|
|
||||||
def serialize(self, serializer):
|
def serialize(self, serializer):
|
||||||
return serializer.serialize_trigger(self)
|
return serializer.serialize_trigger(self)
|
||||||
|
@ -74,10 +74,6 @@ class TaskSpec(object):
|
|||||||
:param wf_spec: A reference to the workflow specification that owns it.
|
:param wf_spec: A reference to the workflow specification that owns it.
|
||||||
:type name: string
|
:type name: string
|
||||||
:param name: A name for the task.
|
:param name: A name for the task.
|
||||||
:type lock: list(str)
|
|
||||||
:param lock: A list of mutex names. The mutex is acquired
|
|
||||||
on entry of execute() and released on leave of
|
|
||||||
execute().
|
|
||||||
:type manual: bool
|
:type manual: bool
|
||||||
:param manual: Whether this task requires a manual action to complete.
|
:param manual: Whether this task requires a manual action to complete.
|
||||||
:type data: dict((str, object))
|
:type data: dict((str, object))
|
||||||
@ -107,7 +103,6 @@ class TaskSpec(object):
|
|||||||
self.defines = kwargs.get('defines', {})
|
self.defines = kwargs.get('defines', {})
|
||||||
self.pre_assign = kwargs.get('pre_assign',[])
|
self.pre_assign = kwargs.get('pre_assign',[])
|
||||||
self.post_assign = kwargs.get('post_assign', [])
|
self.post_assign = kwargs.get('post_assign', [])
|
||||||
self.locks = kwargs.get('lock', [])
|
|
||||||
self.lookahead = 2 # Maximum number of MAYBE predictions.
|
self.lookahead = 2 # Maximum number of MAYBE predictions.
|
||||||
|
|
||||||
# Events.
|
# Events.
|
||||||
@ -213,7 +208,7 @@ class TaskSpec(object):
|
|||||||
if len(self.inputs) < 1:
|
if len(self.inputs) < 1:
|
||||||
raise WorkflowException(self, 'No input task connected.')
|
raise WorkflowException(self, 'No input task connected.')
|
||||||
|
|
||||||
def _predict(self, my_task, seen=None, looked_ahead=0):
|
def _predict(self, my_task, seen=None, looked_ahead=0, mask=TaskState.PREDICTED_MASK):
|
||||||
"""
|
"""
|
||||||
Updates the branch such that all possible future routes are added.
|
Updates the branch such that all possible future routes are added.
|
||||||
|
|
||||||
@ -229,26 +224,25 @@ class TaskSpec(object):
|
|||||||
if seen is None:
|
if seen is None:
|
||||||
seen = []
|
seen = []
|
||||||
|
|
||||||
self._predict_hook(my_task)
|
if my_task._has_state(mask):
|
||||||
if not my_task._is_definite():
|
self._predict_hook(my_task)
|
||||||
|
|
||||||
|
if my_task._is_predicted():
|
||||||
seen.append(self)
|
seen.append(self)
|
||||||
|
|
||||||
look_ahead = my_task._is_definite() or looked_ahead + 1 < self.lookahead
|
look_ahead = my_task._is_definite() or looked_ahead + 1 < self.lookahead
|
||||||
for child in my_task.children:
|
for child in my_task.children:
|
||||||
if not child._is_finished() and child not in seen and look_ahead:
|
if child._has_state(mask) and child not in seen and look_ahead:
|
||||||
child.task_spec._predict(child, seen[:], looked_ahead + 1)
|
child.task_spec._predict(child, seen[:], looked_ahead + 1, mask)
|
||||||
|
|
||||||
def _predict_hook(self, my_task):
|
def _predict_hook(self, my_task):
|
||||||
# If the task's status is not predicted, we default to FUTURE for all it's outputs.
|
# If the task's status is definite, we default to FUTURE for all it's outputs.
|
||||||
# Otherwise, copy my own state to the children.
|
# Otherwise, copy my own state to the children.
|
||||||
if my_task._is_definite():
|
if my_task._is_definite():
|
||||||
best_state = TaskState.FUTURE
|
best_state = TaskState.FUTURE
|
||||||
else:
|
else:
|
||||||
best_state = my_task.state
|
best_state = my_task.state
|
||||||
|
|
||||||
my_task._sync_children(self.outputs, best_state)
|
my_task._sync_children(self.outputs, best_state)
|
||||||
for child in my_task.children:
|
|
||||||
if not child._is_definite():
|
|
||||||
child._set_state(best_state)
|
|
||||||
|
|
||||||
def _update(self, my_task):
|
def _update(self, my_task):
|
||||||
"""
|
"""
|
||||||
@ -281,42 +275,13 @@ class TaskSpec(object):
|
|||||||
assert my_task is not None
|
assert my_task is not None
|
||||||
self.test()
|
self.test()
|
||||||
|
|
||||||
# Acquire locks, if any.
|
|
||||||
for lock in self.locks:
|
|
||||||
mutex = my_task.workflow._get_mutex(lock)
|
|
||||||
if not mutex.testandset():
|
|
||||||
return
|
|
||||||
|
|
||||||
# Assign variables, if so requested.
|
# Assign variables, if so requested.
|
||||||
for assignment in self.pre_assign:
|
for assignment in self.pre_assign:
|
||||||
assignment.assign(my_task, my_task)
|
assignment.assign(my_task, my_task)
|
||||||
|
|
||||||
# Run task-specific code.
|
# Run task-specific code.
|
||||||
self._on_ready_before_hook(my_task)
|
|
||||||
self.reached_event.emit(my_task.workflow, my_task)
|
|
||||||
self._on_ready_hook(my_task)
|
self._on_ready_hook(my_task)
|
||||||
|
self.reached_event.emit(my_task.workflow, my_task)
|
||||||
# Run user code, if any.
|
|
||||||
if self.ready_event.emit(my_task.workflow, my_task):
|
|
||||||
# Assign variables, if so requested.
|
|
||||||
for assignment in self.post_assign:
|
|
||||||
assignment.assign(my_task, my_task)
|
|
||||||
|
|
||||||
# Release locks, if any.
|
|
||||||
for lock in self.locks:
|
|
||||||
mutex = my_task.workflow._get_mutex(lock)
|
|
||||||
mutex.unlock()
|
|
||||||
|
|
||||||
self.finished_event.emit(my_task.workflow, my_task)
|
|
||||||
|
|
||||||
def _on_ready_before_hook(self, my_task):
|
|
||||||
"""
|
|
||||||
A hook into _on_ready() that does the task specific work.
|
|
||||||
|
|
||||||
:type my_task: Task
|
|
||||||
:param my_task: The associated task in the task tree.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _on_ready_hook(self, my_task):
|
def _on_ready_hook(self, my_task):
|
||||||
"""
|
"""
|
||||||
@ -327,6 +292,35 @@ class TaskSpec(object):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def _run(self, my_task):
|
||||||
|
"""
|
||||||
|
Run the task.
|
||||||
|
|
||||||
|
:type my_task: Task
|
||||||
|
:param my_task: The associated task in the task tree.
|
||||||
|
|
||||||
|
:rtype: boolean or None
|
||||||
|
:returns: the value returned by the task spec's run method.
|
||||||
|
"""
|
||||||
|
result = self._run_hook(my_task)
|
||||||
|
# Run user code, if any.
|
||||||
|
if self.ready_event.emit(my_task.workflow, my_task):
|
||||||
|
# Assign variables, if so requested.
|
||||||
|
for assignment in self.post_assign:
|
||||||
|
assignment.assign(my_task, my_task)
|
||||||
|
|
||||||
|
self.finished_event.emit(my_task.workflow, my_task)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _run_hook(self, my_task):
|
||||||
|
"""
|
||||||
|
A hook into _run() that does the task specific work.
|
||||||
|
|
||||||
|
:type my_task: Task
|
||||||
|
:param my_task: The associated task in the task tree.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
def _on_cancel(self, my_task):
|
def _on_cancel(self, my_task):
|
||||||
"""
|
"""
|
||||||
May be called by another task to cancel the operation before it was
|
May be called by another task to cancel the operation before it was
|
||||||
@ -359,20 +353,12 @@ class TaskSpec(object):
|
|||||||
:rtype: boolean
|
:rtype: boolean
|
||||||
:returns: True on success, False otherwise.
|
:returns: True on success, False otherwise.
|
||||||
"""
|
"""
|
||||||
assert my_task is not None
|
|
||||||
|
|
||||||
# We have to set the last task here, because the on_complete_hook
|
|
||||||
# of a loopback task may overwrite what the last_task will be.
|
|
||||||
my_task.workflow.last_task = my_task
|
|
||||||
self._on_complete_hook(my_task)
|
self._on_complete_hook(my_task)
|
||||||
for child in my_task.children:
|
for child in my_task.children:
|
||||||
# Don't like this, but this is the most expedient way of preventing cancelled tasks from reactivation
|
if not child._is_finished():
|
||||||
if child.state != TaskState.CANCELLED:
|
|
||||||
child.task_spec._update(child)
|
child.task_spec._update(child)
|
||||||
my_task.workflow._task_completed_notify(my_task)
|
my_task.workflow._task_completed_notify(my_task)
|
||||||
|
|
||||||
self.completed_event.emit(my_task.workflow, my_task)
|
self.completed_event.emit(my_task.workflow, my_task)
|
||||||
return True
|
|
||||||
|
|
||||||
def _on_complete_hook(self, my_task):
|
def _on_complete_hook(self, my_task):
|
||||||
"""
|
"""
|
||||||
@ -419,7 +405,6 @@ class TaskSpec(object):
|
|||||||
'defines':self.defines,
|
'defines':self.defines,
|
||||||
'pre_assign':self.pre_assign,
|
'pre_assign':self.pre_assign,
|
||||||
'post_assign':self.post_assign,
|
'post_assign':self.post_assign,
|
||||||
'locks':self.locks,
|
|
||||||
'lookahead':self.lookahead,
|
'lookahead':self.lookahead,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -457,7 +442,6 @@ class TaskSpec(object):
|
|||||||
out.defines = s_state.get('defines')
|
out.defines = s_state.get('defines')
|
||||||
out.pre_assign = s_state.get('pre_assign')
|
out.pre_assign = s_state.get('pre_assign')
|
||||||
out.post_assign = s_state.get('post_assign')
|
out.post_assign = s_state.get('post_assign')
|
||||||
out.locks = s_state.get('locks')
|
|
||||||
out.lookahead = s_state.get('lookahead')
|
out.lookahead = s_state.get('lookahead')
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ from SpiffWorkflow.bpmn.parser.BpmnParser import BpmnValidator, full_tag
|
|||||||
from SpiffWorkflow.bpmn.specs.events.EndEvent import EndEvent
|
from SpiffWorkflow.bpmn.specs.events.EndEvent import EndEvent
|
||||||
from SpiffWorkflow.bpmn.specs.events.StartEvent import StartEvent
|
from SpiffWorkflow.bpmn.specs.events.StartEvent import StartEvent
|
||||||
from SpiffWorkflow.bpmn.specs.events.IntermediateEvent import IntermediateThrowEvent, BoundaryEvent, IntermediateCatchEvent
|
from SpiffWorkflow.bpmn.specs.events.IntermediateEvent import IntermediateThrowEvent, BoundaryEvent, IntermediateCatchEvent
|
||||||
|
|
||||||
from SpiffWorkflow.spiff.specs.none_task import NoneTask
|
from SpiffWorkflow.spiff.specs.none_task import NoneTask
|
||||||
from SpiffWorkflow.spiff.specs.manual_task import ManualTask
|
from SpiffWorkflow.spiff.specs.manual_task import ManualTask
|
||||||
from SpiffWorkflow.spiff.specs.user_task import UserTask
|
from SpiffWorkflow.spiff.specs.user_task import UserTask
|
||||||
@ -13,10 +14,18 @@ from SpiffWorkflow.spiff.specs.script_task import ScriptTask
|
|||||||
from SpiffWorkflow.spiff.specs.subworkflow_task import SubWorkflowTask, TransactionSubprocess, CallActivity
|
from SpiffWorkflow.spiff.specs.subworkflow_task import SubWorkflowTask, TransactionSubprocess, CallActivity
|
||||||
from SpiffWorkflow.spiff.specs.service_task import ServiceTask
|
from SpiffWorkflow.spiff.specs.service_task import ServiceTask
|
||||||
from SpiffWorkflow.spiff.specs.events.event_types import SendTask, ReceiveTask
|
from SpiffWorkflow.spiff.specs.events.event_types import SendTask, ReceiveTask
|
||||||
from SpiffWorkflow.spiff.parser.task_spec import SpiffTaskParser, SubWorkflowParser, CallActivityParser, ServiceTaskParser, ScriptTaskParser
|
from SpiffWorkflow.spiff.specs.business_rule_task import BusinessRuleTask
|
||||||
|
from SpiffWorkflow.spiff.parser.task_spec import (
|
||||||
|
SpiffTaskParser,
|
||||||
|
SubWorkflowParser,
|
||||||
|
CallActivityParser,
|
||||||
|
ServiceTaskParser,
|
||||||
|
ScriptTaskParser,
|
||||||
|
BusinessRuleTaskParser
|
||||||
|
)
|
||||||
from SpiffWorkflow.spiff.parser.event_parsers import (SpiffStartEventParser, SpiffEndEventParser, SpiffBoundaryEventParser,
|
from SpiffWorkflow.spiff.parser.event_parsers import (SpiffStartEventParser, SpiffEndEventParser, SpiffBoundaryEventParser,
|
||||||
SpiffIntermediateCatchEventParser, SpiffIntermediateThrowEventParser, SpiffSendTaskParser, SpiffReceiveTaskParser)
|
SpiffIntermediateCatchEventParser, SpiffIntermediateThrowEventParser, SpiffSendTaskParser, SpiffReceiveTaskParser)
|
||||||
from SpiffWorkflow.dmn.specs import BusinessRuleTask
|
|
||||||
|
|
||||||
from SpiffWorkflow.spiff.parser.task_spec import BusinessRuleTaskParser
|
from SpiffWorkflow.spiff.parser.task_spec import BusinessRuleTaskParser
|
||||||
|
|
||||||
@ -44,4 +53,3 @@ class SpiffBpmnParser(BpmnDmnParser):
|
|||||||
full_tag('receiveTask'): (SpiffReceiveTaskParser, ReceiveTask),
|
full_tag('receiveTask'): (SpiffReceiveTaskParser, ReceiveTask),
|
||||||
full_tag('businessRuleTask'): (BusinessRuleTaskParser, BusinessRuleTask)
|
full_tag('businessRuleTask'): (BusinessRuleTaskParser, BusinessRuleTask)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask
|
|
||||||
from SpiffWorkflow.bpmn.parser.TaskParser import TaskParser
|
from SpiffWorkflow.bpmn.parser.TaskParser import TaskParser
|
||||||
from SpiffWorkflow.bpmn.parser.task_parsers import SubprocessParser
|
from SpiffWorkflow.bpmn.parser.task_parsers import SubprocessParser
|
||||||
from SpiffWorkflow.bpmn.parser.util import xpath_eval
|
from SpiffWorkflow.bpmn.parser.util import xpath_eval
|
||||||
|
|
||||||
from SpiffWorkflow.spiff.specs.multiinstance_task import StandardLoopTask, ParallelMultiInstanceTask, SequentialMultiInstanceTask
|
from SpiffWorkflow.spiff.specs.multiinstance_task import StandardLoopTask, ParallelMultiInstanceTask, SequentialMultiInstanceTask
|
||||||
|
from SpiffWorkflow.spiff.specs.business_rule_task import BusinessRuleTask
|
||||||
|
|
||||||
SPIFFWORKFLOW_MODEL_NS = 'http://spiffworkflow.org/bpmn/schema/1.0/core'
|
SPIFFWORKFLOW_MODEL_NS = 'http://spiffworkflow.org/bpmn/schema/1.0/core'
|
||||||
SPIFFWORKFLOW_MODEL_PREFIX = 'spiffworkflow'
|
SPIFFWORKFLOW_MODEL_PREFIX = 'spiffworkflow'
|
||||||
@ -169,13 +169,19 @@ class BusinessRuleTaskParser(SpiffTaskParser):
|
|||||||
|
|
||||||
def create_task(self):
|
def create_task(self):
|
||||||
decision_ref = self.get_decision_ref(self.node)
|
decision_ref = self.get_decision_ref(self.node)
|
||||||
return BusinessRuleTask(self.spec,
|
extensions = self.parse_extensions()
|
||||||
self.get_task_spec_name(),
|
prescript = extensions.get('preScript')
|
||||||
dmnEngine=self.process_parser.parser.get_engine(decision_ref, self.node),
|
postscript = extensions.get('postScript')
|
||||||
lane=self.lane,
|
return BusinessRuleTask(
|
||||||
position=self.position,
|
self.spec,
|
||||||
description=self.node.get('name', None)
|
self.get_task_spec_name(),
|
||||||
)
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_decision_ref(node):
|
def get_decision_ref(node):
|
||||||
|
@ -5,7 +5,6 @@ from SpiffWorkflow.bpmn.serializer.task_spec import (
|
|||||||
SimpleTaskConverter,
|
SimpleTaskConverter,
|
||||||
StartTaskConverter,
|
StartTaskConverter,
|
||||||
EndJoinConverter,
|
EndJoinConverter,
|
||||||
LoopResetTaskConverter,
|
|
||||||
StartEventConverter,
|
StartEventConverter,
|
||||||
EndEventConverter,
|
EndEventConverter,
|
||||||
IntermediateCatchEventConverter,
|
IntermediateCatchEventConverter,
|
||||||
@ -32,6 +31,7 @@ from .task_spec import (
|
|||||||
CallActivityTaskConverter,
|
CallActivityTaskConverter,
|
||||||
ParallelMultiInstanceTaskConverter,
|
ParallelMultiInstanceTaskConverter,
|
||||||
SequentialMultiInstanceTaskConverter,
|
SequentialMultiInstanceTaskConverter,
|
||||||
|
BusinessRuleTaskConverter,
|
||||||
)
|
)
|
||||||
|
|
||||||
from SpiffWorkflow.bpmn.serializer.event_definition import MessageEventDefinitionConverter as DefaultMessageEventDefinitionConverter
|
from SpiffWorkflow.bpmn.serializer.event_definition import MessageEventDefinitionConverter as DefaultMessageEventDefinitionConverter
|
||||||
@ -42,7 +42,6 @@ SPIFF_SPEC_CONFIG['task_specs'] = [
|
|||||||
SimpleTaskConverter,
|
SimpleTaskConverter,
|
||||||
StartTaskConverter,
|
StartTaskConverter,
|
||||||
EndJoinConverter,
|
EndJoinConverter,
|
||||||
LoopResetTaskConverter,
|
|
||||||
StartEventConverter,
|
StartEventConverter,
|
||||||
EndEventConverter,
|
EndEventConverter,
|
||||||
IntermediateCatchEventConverter,
|
IntermediateCatchEventConverter,
|
||||||
@ -66,6 +65,7 @@ SPIFF_SPEC_CONFIG['task_specs'] = [
|
|||||||
StandardLoopTaskConverter,
|
StandardLoopTaskConverter,
|
||||||
ParallelMultiInstanceTaskConverter,
|
ParallelMultiInstanceTaskConverter,
|
||||||
SequentialMultiInstanceTaskConverter,
|
SequentialMultiInstanceTaskConverter,
|
||||||
|
BusinessRuleTaskConverter
|
||||||
]
|
]
|
||||||
SPIFF_SPEC_CONFIG['event_definitions'].remove(DefaultMessageEventDefinitionConverter)
|
SPIFF_SPEC_CONFIG['event_definitions'].remove(DefaultMessageEventDefinitionConverter)
|
||||||
SPIFF_SPEC_CONFIG['event_definitions'].append(MessageEventDefinitionConverter)
|
SPIFF_SPEC_CONFIG['event_definitions'].append(MessageEventDefinitionConverter)
|
@ -1,5 +1,6 @@
|
|||||||
from SpiffWorkflow.bpmn.serializer.helpers.spec import TaskSpecConverter
|
from SpiffWorkflow.bpmn.serializer.helpers.spec import TaskSpecConverter
|
||||||
from SpiffWorkflow.bpmn.serializer.task_spec import MultiInstanceTaskConverter
|
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.none_task import NoneTask
|
||||||
from SpiffWorkflow.spiff.specs.manual_task import ManualTask
|
from SpiffWorkflow.spiff.specs.manual_task import ManualTask
|
||||||
@ -9,6 +10,7 @@ from SpiffWorkflow.spiff.specs.service_task import ServiceTask
|
|||||||
from SpiffWorkflow.spiff.specs.subworkflow_task import SubWorkflowTask, TransactionSubprocess, CallActivity
|
from SpiffWorkflow.spiff.specs.subworkflow_task import SubWorkflowTask, TransactionSubprocess, CallActivity
|
||||||
from SpiffWorkflow.spiff.specs.events.event_types import SendTask, ReceiveTask
|
from SpiffWorkflow.spiff.specs.events.event_types import SendTask, ReceiveTask
|
||||||
from SpiffWorkflow.spiff.specs.multiinstance_task import StandardLoopTask, ParallelMultiInstanceTask, SequentialMultiInstanceTask
|
from SpiffWorkflow.spiff.specs.multiinstance_task import StandardLoopTask, ParallelMultiInstanceTask, SequentialMultiInstanceTask
|
||||||
|
from SpiffWorkflow.spiff.specs.business_rule_task import BusinessRuleTask
|
||||||
|
|
||||||
|
|
||||||
class SpiffBpmnTaskConverter(TaskSpecConverter):
|
class SpiffBpmnTaskConverter(TaskSpecConverter):
|
||||||
@ -39,6 +41,16 @@ class UserTaskConverter(SpiffBpmnTaskConverter):
|
|||||||
super().__init__(UserTask, registry)
|
super().__init__(UserTask, registry)
|
||||||
|
|
||||||
|
|
||||||
|
class BusinessRuleTaskConverter(BaseBusinessRuleTaskConverter, SpiffBpmnTaskConverter):
|
||||||
|
def __init__(self, registry):
|
||||||
|
super().__init__(BusinessRuleTask, registry)
|
||||||
|
|
||||||
|
def to_dict(self, spec):
|
||||||
|
dct = BaseBusinessRuleTaskConverter.to_dict(self, spec)
|
||||||
|
dct.update(SpiffBpmnTaskConverter.to_dict(self, spec))
|
||||||
|
return dct
|
||||||
|
|
||||||
|
|
||||||
class SendTaskConverter(SpiffBpmnTaskConverter):
|
class SendTaskConverter(SpiffBpmnTaskConverter):
|
||||||
|
|
||||||
def __init__(self, registry, typename=None):
|
def __init__(self, registry, typename=None):
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
from SpiffWorkflow.spiff.specs.spiff_task import SpiffBpmnTask
|
||||||
|
from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask as DefaultBusinessRuleTask
|
||||||
|
|
||||||
|
class BusinessRuleTask(DefaultBusinessRuleTask, SpiffBpmnTask):
|
||||||
|
pass
|
@ -77,9 +77,9 @@ class TaskState:
|
|||||||
CANCELLED = 64
|
CANCELLED = 64
|
||||||
|
|
||||||
FINISHED_MASK = CANCELLED | COMPLETED
|
FINISHED_MASK = CANCELLED | COMPLETED
|
||||||
DEFINITE_MASK = FUTURE | WAITING | READY | FINISHED_MASK
|
DEFINITE_MASK = FUTURE | WAITING | READY
|
||||||
PREDICTED_MASK = FUTURE | LIKELY | MAYBE
|
PREDICTED_MASK = LIKELY | MAYBE
|
||||||
NOT_FINISHED_MASK = PREDICTED_MASK | WAITING | READY
|
NOT_FINISHED_MASK = PREDICTED_MASK | DEFINITE_MASK
|
||||||
ANY_MASK = FINISHED_MASK | NOT_FINISHED_MASK
|
ANY_MASK = FINISHED_MASK | NOT_FINISHED_MASK
|
||||||
|
|
||||||
|
|
||||||
@ -292,43 +292,6 @@ class Task(object, metaclass=DeprecatedMetaTask):
|
|||||||
self.data = DeepMerge.merge(self.data, data)
|
self.data = DeepMerge.merge(self.data, data)
|
||||||
data_log.info('Data update', extra=self.log_info())
|
data_log.info('Data update', extra=self.log_info())
|
||||||
|
|
||||||
def set_children_future(self):
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
self._set_state(TaskState.FUTURE)
|
|
||||||
self.task_spec._update(self)
|
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return Task.Iterator(self)
|
return Task.Iterator(self)
|
||||||
|
|
||||||
@ -366,9 +329,7 @@ class Task(object, metaclass=DeprecatedMetaTask):
|
|||||||
self.children.remove(task)
|
self.children.remove(task)
|
||||||
|
|
||||||
def _has_state(self, state):
|
def _has_state(self, state):
|
||||||
"""
|
"""Returns True if the Task has the given state flag set."""
|
||||||
Returns True if the Task has the given state flag set.
|
|
||||||
"""
|
|
||||||
return (self.state & state) != 0
|
return (self.state & state) != 0
|
||||||
|
|
||||||
def _is_finished(self):
|
def _is_finished(self):
|
||||||
@ -380,6 +341,43 @@ class Task(object, metaclass=DeprecatedMetaTask):
|
|||||||
def _is_definite(self):
|
def _is_definite(self):
|
||||||
return self._has_state(TaskState.DEFINITE_MASK)
|
return self._has_state(TaskState.DEFINITE_MASK)
|
||||||
|
|
||||||
|
def set_children_future(self):
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
self._set_state(TaskState.FUTURE)
|
||||||
|
self.task_spec._update(self)
|
||||||
|
|
||||||
def _add_child(self, task_spec, state=TaskState.MAYBE):
|
def _add_child(self, task_spec, state=TaskState.MAYBE):
|
||||||
"""
|
"""
|
||||||
Adds a new child and assigns the given TaskSpec to it.
|
Adds a new child and assigns the given TaskSpec to it.
|
||||||
@ -391,17 +389,60 @@ class Task(object, metaclass=DeprecatedMetaTask):
|
|||||||
:rtype: Task
|
:rtype: Task
|
||||||
:returns: The new child task.
|
:returns: The new child task.
|
||||||
"""
|
"""
|
||||||
if task_spec is None:
|
|
||||||
raise ValueError(self, '_add_child() requires a TaskSpec')
|
|
||||||
if self._is_predicted() and state & TaskState.PREDICTED_MASK == 0:
|
if self._is_predicted() and state & TaskState.PREDICTED_MASK == 0:
|
||||||
msg = 'Attempt to add non-predicted child to predicted task'
|
raise WorkflowException('Attempt to add non-predicted child to predicted task', task_spec=self.task_spec)
|
||||||
raise WorkflowException(msg, task_spec=self.task_spec)
|
|
||||||
task = Task(self.workflow, task_spec, self, state=state)
|
task = Task(self.workflow, task_spec, self, state=state)
|
||||||
task.thread_id = self.thread_id
|
task.thread_id = self.thread_id
|
||||||
if state == TaskState.READY:
|
if state == TaskState.READY:
|
||||||
task._ready()
|
task._ready()
|
||||||
return task
|
return task
|
||||||
|
|
||||||
|
def _sync_children(self, task_specs, state=TaskState.MAYBE):
|
||||||
|
"""
|
||||||
|
This method syncs up the task's children with the given list of task
|
||||||
|
specs. In other words::
|
||||||
|
|
||||||
|
- Add one child for each given TaskSpec, unless that child already
|
||||||
|
exists.
|
||||||
|
- Remove all children for which there is no spec in the given list,
|
||||||
|
unless it is a "triggered" task.
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
It is an error if the task has a non-predicted child that is
|
||||||
|
not given in the TaskSpecs.
|
||||||
|
|
||||||
|
:type task_specs: list(TaskSpec)
|
||||||
|
:param task_specs: The list of task specs that may become children.
|
||||||
|
:type state: integer
|
||||||
|
:param state: The bitmask of states for the new children.
|
||||||
|
"""
|
||||||
|
if task_specs is None:
|
||||||
|
raise ValueError('"task_specs" argument is None')
|
||||||
|
new_children = task_specs[:]
|
||||||
|
|
||||||
|
# Create a list of all children that are no longer needed.
|
||||||
|
unneeded_children = []
|
||||||
|
for child in self.children:
|
||||||
|
if child.triggered:
|
||||||
|
# Triggered tasks are never removed.
|
||||||
|
pass
|
||||||
|
elif child.task_spec in new_children:
|
||||||
|
# If the task already exists, remove it from to-be-added and update its state
|
||||||
|
new_children.remove(child.task_spec)
|
||||||
|
if not child._is_finished():
|
||||||
|
child._set_state(state)
|
||||||
|
else:
|
||||||
|
if child._is_definite():
|
||||||
|
# Definite tasks must not be removed, so they HAVE to be in the given task spec list.
|
||||||
|
raise WorkflowException(f'removal of non-predicted child {child}', task_spec=self.task_spec)
|
||||||
|
unneeded_children.append(child)
|
||||||
|
|
||||||
|
# Update children accordingly
|
||||||
|
for child in unneeded_children:
|
||||||
|
self.children.remove(child)
|
||||||
|
for task_spec in new_children:
|
||||||
|
self._add_child(task_spec, state)
|
||||||
|
|
||||||
def _assign_new_thread_id(self, recursive=True):
|
def _assign_new_thread_id(self, recursive=True):
|
||||||
"""
|
"""
|
||||||
Assigns a new thread id to the task.
|
Assigns a new thread id to the task.
|
||||||
@ -419,78 +460,6 @@ class Task(object, metaclass=DeprecatedMetaTask):
|
|||||||
child.thread_id = self.thread_id
|
child.thread_id = self.thread_id
|
||||||
return self.thread_id
|
return self.thread_id
|
||||||
|
|
||||||
def _sync_children(self, task_specs, state=TaskState.MAYBE):
|
|
||||||
"""
|
|
||||||
This method syncs up the task's children with the given list of task
|
|
||||||
specs. In other words::
|
|
||||||
|
|
||||||
- Add one child for each given TaskSpec, unless that child already
|
|
||||||
exists.
|
|
||||||
- Remove all children for which there is no spec in the given list,
|
|
||||||
unless it is a "triggered" task.
|
|
||||||
- Handle looping back to previous tasks, so we don't end up with
|
|
||||||
an infinitely large tree.
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
It is an error if the task has a non-predicted child that is
|
|
||||||
not given in the TaskSpecs.
|
|
||||||
|
|
||||||
:type task_specs: list(TaskSpec)
|
|
||||||
:param task_specs: The list of task specs that may become children.
|
|
||||||
:type state: integer
|
|
||||||
:param state: The bitmask of states for the new children.
|
|
||||||
"""
|
|
||||||
if task_specs is None:
|
|
||||||
raise ValueError('"task_specs" argument is None')
|
|
||||||
new_children = task_specs[:]
|
|
||||||
|
|
||||||
# If a child task_spec is also an ancestor, we are looping back,
|
|
||||||
# replace those specs with a loopReset task.
|
|
||||||
root_task = self._get_root()
|
|
||||||
for index, task_spec in enumerate(new_children):
|
|
||||||
ancestor_task = self._find_ancestor(task_spec)
|
|
||||||
if ancestor_task and ancestor_task != root_task:
|
|
||||||
destination = ancestor_task
|
|
||||||
new_spec = self.workflow.get_reset_task_spec(destination)
|
|
||||||
new_spec.outputs = []
|
|
||||||
new_spec.inputs = task_spec.inputs
|
|
||||||
new_children[index] = new_spec
|
|
||||||
|
|
||||||
# Create a list of all children that are no longer needed.
|
|
||||||
unneeded_children = []
|
|
||||||
for child in self.children:
|
|
||||||
# Triggered tasks are never removed.
|
|
||||||
if child.triggered:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# If the task already exists, remove it from to-be-added
|
|
||||||
if child.task_spec in new_children:
|
|
||||||
new_children.remove(child.task_spec)
|
|
||||||
# We should set the state here but that breaks everything
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Definite tasks must not be removed, so they HAVE to be in the given task spec list.
|
|
||||||
if child._is_definite():
|
|
||||||
raise WorkflowException(f'removal of non-predicted child {child}', task_spec=self.task_spec)
|
|
||||||
unneeded_children.append(child)
|
|
||||||
|
|
||||||
# Remove and add the children accordingly.
|
|
||||||
for child in unneeded_children:
|
|
||||||
self.children.remove(child)
|
|
||||||
for task_spec in new_children:
|
|
||||||
self._add_child(task_spec, state)
|
|
||||||
|
|
||||||
def _set_likely_task(self, task_specs):
|
|
||||||
if not isinstance(task_specs, list):
|
|
||||||
task_specs = [task_specs]
|
|
||||||
for task_spec in task_specs:
|
|
||||||
for child in self.children:
|
|
||||||
if child.task_spec != task_spec:
|
|
||||||
continue
|
|
||||||
if child._is_definite():
|
|
||||||
continue
|
|
||||||
child._set_state(TaskState.LIKELY)
|
|
||||||
|
|
||||||
def _is_descendant_of(self, parent):
|
def _is_descendant_of(self, parent):
|
||||||
"""
|
"""
|
||||||
Returns True if parent is in the list of ancestors, returns False
|
Returns True if parent is in the list of ancestors, returns False
|
||||||
@ -574,15 +543,6 @@ class Task(object, metaclass=DeprecatedMetaTask):
|
|||||||
return self.parent
|
return self.parent
|
||||||
return self.parent._find_ancestor_from_name(name)
|
return self.parent._find_ancestor_from_name(name)
|
||||||
|
|
||||||
def _ready(self):
|
|
||||||
"""
|
|
||||||
Marks the task as ready for execution.
|
|
||||||
"""
|
|
||||||
if self._has_state(TaskState.COMPLETED) or self._has_state(TaskState.CANCELLED):
|
|
||||||
return
|
|
||||||
self._set_state(TaskState.READY)
|
|
||||||
self.task_spec._on_ready(self)
|
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
return str(self.task_spec.name)
|
return str(self.task_spec.name)
|
||||||
|
|
||||||
@ -590,14 +550,10 @@ class Task(object, metaclass=DeprecatedMetaTask):
|
|||||||
return str(self.task_spec.description)
|
return str(self.task_spec.description)
|
||||||
|
|
||||||
def get_state_name(self):
|
def get_state_name(self):
|
||||||
"""
|
"""Returns a textual representation of this Task's state."""
|
||||||
Returns a textual representation of this Task's state.
|
|
||||||
"""
|
|
||||||
state_name = []
|
|
||||||
for state, name in list(TaskStateNames.items()):
|
for state, name in list(TaskStateNames.items()):
|
||||||
if self._has_state(state):
|
if self._has_state(state):
|
||||||
state_name.append(name)
|
return name
|
||||||
return '|'.join(state_name)
|
|
||||||
|
|
||||||
def get_spec_data(self, name=None, default=None):
|
def get_spec_data(self, name=None, default=None):
|
||||||
"""
|
"""
|
||||||
@ -648,37 +604,54 @@ class Task(object, metaclass=DeprecatedMetaTask):
|
|||||||
"""
|
"""
|
||||||
return self.data.get(name, default)
|
return self.data.get(name, default)
|
||||||
|
|
||||||
def cancel(self):
|
def _ready(self):
|
||||||
"""
|
"""Marks the task as ready for execution."""
|
||||||
Cancels the item if it was not yet completed, and removes
|
if self._has_state(TaskState.COMPLETED) or self._has_state(TaskState.CANCELLED):
|
||||||
any children that are LIKELY.
|
|
||||||
"""
|
|
||||||
if self._is_finished():
|
|
||||||
for child in self.children:
|
|
||||||
child.cancel()
|
|
||||||
return
|
return
|
||||||
self._set_state(TaskState.CANCELLED)
|
self._set_state(TaskState.READY)
|
||||||
self._drop_children()
|
self.task_spec._on_ready(self)
|
||||||
self.task_spec._on_cancel(self)
|
|
||||||
|
|
||||||
def complete(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
Called by the associated task to let us know that its state
|
Execute the task.
|
||||||
has changed (e.g. from FUTURE to COMPLETED.)
|
|
||||||
|
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
|
||||||
|
:returns: the value returned by the task spec's run method
|
||||||
"""
|
"""
|
||||||
self._set_state(TaskState.COMPLETED)
|
|
||||||
# I am taking back my previous comment about running the task after it's completed being "CRAZY"
|
|
||||||
# Turns out that tasks are in fact supposed to be complete at this point and I've been wrong all along
|
|
||||||
# about when tasks should actually be executed
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
retval = self.task_spec._on_complete(self)
|
retval = self.task_spec._run(self)
|
||||||
extra = self.log_info({
|
extra = self.log_info({
|
||||||
'action': 'Complete',
|
'action': 'Complete',
|
||||||
'elapsed': time.time() - start
|
'elapsed': time.time() - start
|
||||||
})
|
})
|
||||||
metrics.debug('', extra=extra)
|
metrics.debug('', extra=extra)
|
||||||
|
if retval is None:
|
||||||
|
self._set_state(TaskState.WAITING)
|
||||||
|
else:
|
||||||
|
# If we add an error state, the we can move the task to COMPLETE or ERROR
|
||||||
|
# according to the return value.
|
||||||
|
self.complete()
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
"""Cancels the item if it was not yet completed, and removes any children that are LIKELY."""
|
||||||
|
if self._is_finished():
|
||||||
|
for child in self.children:
|
||||||
|
child.cancel()
|
||||||
|
else:
|
||||||
|
self._set_state(TaskState.CANCELLED)
|
||||||
|
self._drop_children()
|
||||||
|
self.task_spec._on_cancel(self)
|
||||||
|
|
||||||
|
def complete(self):
|
||||||
|
"""Marks this task complete."""
|
||||||
|
self._set_state(TaskState.COMPLETED)
|
||||||
|
self.task_spec._on_complete(self)
|
||||||
|
self.workflow.last_task = self
|
||||||
|
|
||||||
def trigger(self, *args):
|
def trigger(self, *args):
|
||||||
"""
|
"""
|
||||||
If recursive is True, the state is applied to the tree recursively.
|
If recursive is True, the state is applied to the tree recursively.
|
||||||
|
@ -1,30 +1,22 @@
|
|||||||
from builtins import object
|
from threading import Lock
|
||||||
|
|
||||||
try:
|
class mutex(object):
|
||||||
# python 2
|
|
||||||
from mutex import mutex
|
|
||||||
|
|
||||||
except ImportError:
|
def __init__(self):
|
||||||
# python 3
|
self.lock = Lock()
|
||||||
from threading import Lock
|
|
||||||
|
|
||||||
class mutex(object):
|
def lock(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def __init__(self):
|
def test(self):
|
||||||
self.lock = Lock()
|
has = self.lock.acquire(blocking=False)
|
||||||
|
if has:
|
||||||
def lock(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def test(self):
|
|
||||||
has = self.lock.acquire(blocking=False)
|
|
||||||
if has:
|
|
||||||
self.lock.release()
|
|
||||||
|
|
||||||
return has
|
|
||||||
|
|
||||||
def testandset(self):
|
|
||||||
return self.lock.acquire(blocking=False)
|
|
||||||
|
|
||||||
def unlock(self):
|
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
|
|
||||||
|
return has
|
||||||
|
|
||||||
|
def testandset(self):
|
||||||
|
return self.lock.acquire(blocking=False)
|
||||||
|
|
||||||
|
def unlock(self):
|
||||||
|
self.lock.release()
|
||||||
|
@ -20,14 +20,14 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .specs.Simple import Simple
|
from .specs.Simple import Simple
|
||||||
from .specs.LoopResetTask import LoopResetTask
|
|
||||||
from .task import Task, TaskState
|
from .task import Task, TaskState
|
||||||
from .util.compat import mutex
|
from .util.compat import mutex
|
||||||
from .util.event import Event
|
from .util.event import Event
|
||||||
from .exceptions import WorkflowException
|
from .exceptions import TaskNotFoundException, WorkflowException
|
||||||
|
|
||||||
logger = logging.getLogger('spiff')
|
logger = logging.getLogger('spiff')
|
||||||
|
|
||||||
|
|
||||||
class Workflow(object):
|
class Workflow(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -54,29 +54,25 @@ class Workflow(object):
|
|||||||
self.outer_workflow = kwargs.get('parent', self)
|
self.outer_workflow = kwargs.get('parent', self)
|
||||||
self.locks = {}
|
self.locks = {}
|
||||||
self.last_task = None
|
self.last_task = None
|
||||||
if deserializing:
|
if 'Root' in workflow_spec.task_specs:
|
||||||
assert 'Root' in workflow_spec.task_specs
|
root = workflow_spec.task_specs['Root']
|
||||||
root = workflow_spec.task_specs['Root'] # Probably deserialized
|
|
||||||
else:
|
else:
|
||||||
if 'Root' in workflow_spec.task_specs:
|
root = Simple(workflow_spec, 'Root')
|
||||||
root = workflow_spec.task_specs['Root']
|
|
||||||
else:
|
|
||||||
root = Simple(workflow_spec, 'Root')
|
|
||||||
logger.info('Initialize', extra=self.log_info())
|
|
||||||
|
|
||||||
# Setting TaskState.COMPLETED prevents the root task from being executed.
|
# Setting TaskState.COMPLETED prevents the root task from being executed.
|
||||||
self.task_tree = Task(self, root, state=TaskState.COMPLETED)
|
self.task_tree = Task(self, root, state=TaskState.COMPLETED)
|
||||||
|
start = self.task_tree._add_child(self.spec.start, state=TaskState.FUTURE)
|
||||||
self.success = True
|
self.success = True
|
||||||
self.debug = False
|
self.debug = False
|
||||||
|
|
||||||
# Events.
|
# Events.
|
||||||
self.completed_event = Event()
|
self.completed_event = Event()
|
||||||
|
|
||||||
start = self.task_tree._add_child(self.spec.start, state=TaskState.FUTURE)
|
if not deserializing:
|
||||||
|
self._predict()
|
||||||
self.spec.start._predict(start)
|
if 'parent' not in kwargs:
|
||||||
if 'parent' not in kwargs:
|
start.task_spec._update(start)
|
||||||
start.task_spec._update(start)
|
logger.info('Initialize', extra=self.log_info())
|
||||||
|
|
||||||
self.task_mapping = self._get_task_mapping()
|
self.task_mapping = self._get_task_mapping()
|
||||||
|
|
||||||
@ -108,6 +104,10 @@ class Workflow(object):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _predict(self, mask=TaskState.NOT_FINISHED_MASK):
|
||||||
|
for task in Workflow.get_tasks(self,TaskState.NOT_FINISHED_MASK):
|
||||||
|
task.task_spec._predict(task, mask=mask)
|
||||||
|
|
||||||
def _get_waiting_tasks(self):
|
def _get_waiting_tasks(self):
|
||||||
waiting = Task.Iterator(self.task_tree, TaskState.WAITING)
|
waiting = Task.Iterator(self.task_tree, TaskState.WAITING)
|
||||||
return [w for w in waiting]
|
return [w for w in waiting]
|
||||||
@ -195,24 +195,6 @@ class Workflow(object):
|
|||||||
"""
|
"""
|
||||||
return self.spec.get_task_spec_from_name(name)
|
return self.spec.get_task_spec_from_name(name)
|
||||||
|
|
||||||
def get_task(self, id,tasklist=None):
|
|
||||||
"""
|
|
||||||
Returns the task with the given id.
|
|
||||||
|
|
||||||
:type id:integer
|
|
||||||
:param id: The id of a task.
|
|
||||||
:param tasklist: Optional cache of get_tasks for operations
|
|
||||||
where we are calling multiple times as when we
|
|
||||||
are deserializing the workflow
|
|
||||||
:rtype: Task
|
|
||||||
:returns: The task with the given id.
|
|
||||||
"""
|
|
||||||
if tasklist:
|
|
||||||
tasks = [task for task in tasklist if task.id == id]
|
|
||||||
else:
|
|
||||||
tasks = [task for task in self.get_tasks() if task.id == id]
|
|
||||||
return tasks[0] if len(tasks) == 1 else None
|
|
||||||
|
|
||||||
def get_tasks_from_spec_name(self, name):
|
def get_tasks_from_spec_name(self, name):
|
||||||
"""
|
"""
|
||||||
Returns all tasks whose spec has the given name.
|
Returns all tasks whose spec has the given name.
|
||||||
@ -222,15 +204,7 @@ class Workflow(object):
|
|||||||
:rtype: list[Task]
|
:rtype: list[Task]
|
||||||
:returns: A list of tasks that relate to the spec with the given name.
|
:returns: A list of tasks that relate to the spec with the given name.
|
||||||
"""
|
"""
|
||||||
return [task for task in self.get_tasks_iterator()
|
return [task for task in self.get_tasks_iterator() if task.task_spec.name == name]
|
||||||
if task.task_spec.name == name]
|
|
||||||
|
|
||||||
def empty(self,str):
|
|
||||||
if str == None:
|
|
||||||
return True
|
|
||||||
if str == '':
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_tasks(self, state=TaskState.ANY_MASK):
|
def get_tasks(self, state=TaskState.ANY_MASK):
|
||||||
"""
|
"""
|
||||||
@ -243,38 +217,6 @@ class Workflow(object):
|
|||||||
"""
|
"""
|
||||||
return [t for t in Task.Iterator(self.task_tree, state)]
|
return [t for t in Task.Iterator(self.task_tree, state)]
|
||||||
|
|
||||||
def reset_task_from_id(self, task_id):
|
|
||||||
"""
|
|
||||||
Runs the task with the given id.
|
|
||||||
|
|
||||||
:type task_id: integer
|
|
||||||
:param task_id: The id of the Task object.
|
|
||||||
"""
|
|
||||||
if task_id is None:
|
|
||||||
raise WorkflowException('task_id is None', task_spec=self.spec)
|
|
||||||
data = {}
|
|
||||||
if self.last_task and self.last_task.data:
|
|
||||||
data = self.last_task.data
|
|
||||||
for task in self.task_tree:
|
|
||||||
if task.id == task_id:
|
|
||||||
return task.reset_token(data)
|
|
||||||
msg = 'A task with the given task_id (%s) was not found' % task_id
|
|
||||||
raise WorkflowException(msg, task_spec=self.spec)
|
|
||||||
|
|
||||||
def get_reset_task_spec(self, destination):
|
|
||||||
"""
|
|
||||||
Returns a task, that once complete, will reset the workflow back
|
|
||||||
to a previously completed task.
|
|
||||||
:param destination: Task to reset to, on complete.
|
|
||||||
:return: TaskSpec
|
|
||||||
"""
|
|
||||||
name = "return_to_" + destination.task_spec.name
|
|
||||||
spec = self.get_task_spec_from_name(name)
|
|
||||||
if not spec:
|
|
||||||
spec = LoopResetTask(self.spec, name, destination.id,
|
|
||||||
destination.task_spec.name)
|
|
||||||
return spec
|
|
||||||
|
|
||||||
def get_tasks_iterator(self, state=TaskState.ANY_MASK):
|
def get_tasks_iterator(self, state=TaskState.ANY_MASK):
|
||||||
"""
|
"""
|
||||||
Returns a iterator of Task objects with the given state.
|
Returns a iterator of Task objects with the given state.
|
||||||
@ -286,22 +228,54 @@ class Workflow(object):
|
|||||||
"""
|
"""
|
||||||
return Task.Iterator(self.task_tree, state)
|
return Task.Iterator(self.task_tree, state)
|
||||||
|
|
||||||
def complete_task_from_id(self, task_id):
|
def get_task_from_id(self, task_id, tasklist=None):
|
||||||
|
"""
|
||||||
|
Returns the task with the given id.
|
||||||
|
|
||||||
|
:type id:integer
|
||||||
|
:param id: The id of a task.
|
||||||
|
:param tasklist: Optional cache of get_tasks for operations
|
||||||
|
where we are calling multiple times as when we
|
||||||
|
are deserializing the workflow
|
||||||
|
:rtype: Task
|
||||||
|
:returns: The task with the given id.
|
||||||
|
"""
|
||||||
|
if task_id is None:
|
||||||
|
raise WorkflowException('task_id is None', task_spec=self.spec)
|
||||||
|
tasklist = tasklist or self.task_tree
|
||||||
|
for task in self.task_tree:
|
||||||
|
if task.id == task_id:
|
||||||
|
return task
|
||||||
|
msg = 'A task with the given task_id (%s) was not found' % task_id
|
||||||
|
raise TaskNotFoundException(msg, task_spec=self.spec)
|
||||||
|
|
||||||
|
def run_task_from_id(self, task_id):
|
||||||
"""
|
"""
|
||||||
Runs the task with the given id.
|
Runs the task with the given id.
|
||||||
|
|
||||||
:type task_id: integer
|
:type task_id: integer
|
||||||
:param task_id: The id of the Task object.
|
:param task_id: The id of the Task object.
|
||||||
"""
|
"""
|
||||||
if task_id is None:
|
task = self.get_task_from_id(task_id)
|
||||||
raise WorkflowException('task_id is None', task_spec=self.spec)
|
return task.run()
|
||||||
for task in self.task_tree:
|
|
||||||
if task.id == task_id:
|
|
||||||
return task.complete()
|
|
||||||
msg = 'A task with the given task_id (%s) was not found' % task_id
|
|
||||||
raise WorkflowException(msg, task_spec=self.spec)
|
|
||||||
|
|
||||||
def complete_next(self, pick_up=True, halt_on_manual=True):
|
def reset_task_from_id(self, task_id):
|
||||||
|
"""
|
||||||
|
Runs the task with the given id.
|
||||||
|
|
||||||
|
:type task_id: integer
|
||||||
|
:param task_id: The id of the Task object.
|
||||||
|
"""
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
def run_next(self, pick_up=True, halt_on_manual=True):
|
||||||
"""
|
"""
|
||||||
Runs the next task.
|
Runs the next task.
|
||||||
Returns True if completed, False otherwise.
|
Returns True if completed, False otherwise.
|
||||||
@ -329,7 +303,7 @@ class Workflow(object):
|
|||||||
self.last_task = None
|
self.last_task = None
|
||||||
if task is not None:
|
if task is not None:
|
||||||
if not (halt_on_manual and task.task_spec.manual):
|
if not (halt_on_manual and task.task_spec.manual):
|
||||||
if task.complete():
|
if task.run():
|
||||||
self.last_task = task
|
self.last_task = task
|
||||||
return True
|
return True
|
||||||
blacklist.append(task)
|
blacklist.append(task)
|
||||||
@ -340,7 +314,7 @@ class Workflow(object):
|
|||||||
if task._is_descendant_of(blacklisted_task):
|
if task._is_descendant_of(blacklisted_task):
|
||||||
continue
|
continue
|
||||||
if not (halt_on_manual and task.task_spec.manual):
|
if not (halt_on_manual and task.task_spec.manual):
|
||||||
if task.complete():
|
if task.run():
|
||||||
self.last_task = task
|
self.last_task = task
|
||||||
return True
|
return True
|
||||||
blacklist.append(task)
|
blacklist.append(task)
|
||||||
@ -353,7 +327,7 @@ class Workflow(object):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def complete_all(self, pick_up=True, halt_on_manual=True):
|
def run_all(self, pick_up=True, halt_on_manual=True):
|
||||||
"""
|
"""
|
||||||
Runs all branches until completion. This is a convenience wrapper
|
Runs all branches until completion. This is a convenience wrapper
|
||||||
around :meth:`complete_next`, and the pick_up argument is passed
|
around :meth:`complete_next`, and the pick_up argument is passed
|
||||||
@ -366,7 +340,7 @@ class Workflow(object):
|
|||||||
complete any tasks that have manual=True.
|
complete any tasks that have manual=True.
|
||||||
See :meth:`SpiffWorkflow.specs.TaskSpec.__init__`
|
See :meth:`SpiffWorkflow.specs.TaskSpec.__init__`
|
||||||
"""
|
"""
|
||||||
while self.complete_next(pick_up, halt_on_manual):
|
while self.run_next(pick_up, halt_on_manual):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_dump(self):
|
def get_dump(self):
|
||||||
|
@ -19,7 +19,7 @@ workflow = Workflow(spec)
|
|||||||
# Execute until all tasks are done or require manual intervention.
|
# Execute until all tasks are done or require manual intervention.
|
||||||
# For the sake of this tutorial, we ignore the "manual" flag on the
|
# For the sake of this tutorial, we ignore the "manual" flag on the
|
||||||
# tasks. In practice, you probably don't want to do that.
|
# 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)
|
||||||
|
|
||||||
# Alternatively, this is what a UI would do for a manual task.
|
# Alternatively, this is what a UI would do for a manual task.
|
||||||
#workflow.complete_task_from_id(...)
|
#workflow.complete_task_from_id(...)
|
||||||
|
@ -21,7 +21,10 @@ setup(name='SpiffWorkflow',
|
|||||||
author_email='dan@sartography.com',
|
author_email='dan@sartography.com',
|
||||||
license='lGPLv2',
|
license='lGPLv2',
|
||||||
packages=find_packages(exclude=['tests', 'tests.*']),
|
packages=find_packages(exclude=['tests', 'tests.*']),
|
||||||
package_data={'SpiffWorkflow.bpmn.parser.schema': ['*.xsd']},
|
package_data={
|
||||||
|
'SpiffWorkflow.bpmn.parser': ['schema/*.xsd'],
|
||||||
|
'SpiffWorkflow.dmn.parser': ['schema/*.xsd'],
|
||||||
|
},
|
||||||
install_requires=['configparser', 'lxml', 'celery',
|
install_requires=['configparser', 'lxml', 'celery',
|
||||||
# required for python 3.7 - https://stackoverflow.com/a/73932581
|
# required for python 3.7 - https://stackoverflow.com/a/73932581
|
||||||
'importlib-metadata<5.0; python_version <= "3.7"'],
|
'importlib-metadata<5.0; python_version <= "3.7"'],
|
||||||
|
@ -1,103 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from builtins import object
|
|
||||||
import sys
|
|
||||||
import unittest
|
|
||||||
import os
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
|
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
from SpiffWorkflow.specs.WorkflowSpec import WorkflowSpec
|
|
||||||
from SpiffWorkflow.task import Task
|
|
||||||
from SpiffWorkflow.serializer.prettyxml import XmlSerializer
|
|
||||||
from tests.SpiffWorkflow.util import run_workflow
|
|
||||||
|
|
||||||
|
|
||||||
class WorkflowTestData(object):
|
|
||||||
|
|
||||||
def __init__(self, filename, spec, path, data):
|
|
||||||
self.filename = filename
|
|
||||||
self.spec = spec
|
|
||||||
self.path = path
|
|
||||||
self.data = data
|
|
||||||
|
|
||||||
|
|
||||||
class PatternTest(unittest.TestCase):
|
|
||||||
maxDiff = None
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
Task.id_pool = 0
|
|
||||||
Task.thread_id_pool = 0
|
|
||||||
self.xml_path = ['data/spiff/control-flow',
|
|
||||||
'data/spiff/data',
|
|
||||||
'data/spiff/resource',
|
|
||||||
'data/spiff']
|
|
||||||
self.workflows = []
|
|
||||||
|
|
||||||
for basedir in self.xml_path:
|
|
||||||
dirname = os.path.join(os.path.dirname(__file__), basedir)
|
|
||||||
|
|
||||||
for filename in os.listdir(dirname):
|
|
||||||
if not filename.endswith(('.xml', '.py')):
|
|
||||||
continue
|
|
||||||
if filename.endswith('__.py'):
|
|
||||||
continue
|
|
||||||
filename = os.path.join(dirname, filename)
|
|
||||||
self.load_workflow_spec(filename)
|
|
||||||
|
|
||||||
def load_workflow_spec(self, filename):
|
|
||||||
# Load the .path file.
|
|
||||||
path_file = os.path.splitext(filename)[0] + '.path'
|
|
||||||
if os.path.exists(path_file):
|
|
||||||
with open(path_file) as fp:
|
|
||||||
expected_path = fp.read()
|
|
||||||
else:
|
|
||||||
expected_path = None
|
|
||||||
|
|
||||||
# Load the .data file.
|
|
||||||
data_file = os.path.splitext(filename)[0] + '.data'
|
|
||||||
if os.path.exists(data_file):
|
|
||||||
with open(data_file) as fp:
|
|
||||||
expected_data = fp.read()
|
|
||||||
else:
|
|
||||||
expected_data = None
|
|
||||||
|
|
||||||
# Test patterns that are defined in XML format.
|
|
||||||
if filename.endswith('.xml'):
|
|
||||||
with open(filename) as fp:
|
|
||||||
xml = etree.parse(fp).getroot()
|
|
||||||
serializer = XmlSerializer()
|
|
||||||
wf_spec = WorkflowSpec.deserialize(
|
|
||||||
serializer, xml, filename=filename)
|
|
||||||
|
|
||||||
# Test patterns that are defined in Python.
|
|
||||||
elif filename.endswith('.py'):
|
|
||||||
with open(filename) as fp:
|
|
||||||
code = compile(fp.read(), filename, 'exec')
|
|
||||||
thedict = {}
|
|
||||||
result = eval(code, thedict)
|
|
||||||
wf_spec = thedict['TestWorkflowSpec']()
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise Exception('unsuported specification format', filename)
|
|
||||||
|
|
||||||
test_data = WorkflowTestData(
|
|
||||||
filename, wf_spec, expected_path, expected_data)
|
|
||||||
self.workflows.append(test_data)
|
|
||||||
|
|
||||||
def testWorkflowSpec(self):
|
|
||||||
for test in self.workflows:
|
|
||||||
print(test.filename)
|
|
||||||
run_workflow(self, test.spec, test.path, test.data)
|
|
||||||
|
|
||||||
|
|
||||||
def suite():
|
|
||||||
return unittest.TestLoader().loadTestsFromTestCase(PatternTest)
|
|
||||||
if __name__ == '__main__':
|
|
||||||
if len(sys.argv) == 2:
|
|
||||||
test = PatternTest('run_pattern')
|
|
||||||
test.setUp()
|
|
||||||
test.run_pattern(sys.argv[1])
|
|
||||||
sys.exit(0)
|
|
||||||
unittest.TextTestRunner(verbosity=2).run(suite())
|
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
|
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
|
||||||
from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase
|
from .BpmnWorkflowTestCase import BpmnWorkflowTestCase
|
||||||
|
|
||||||
__author__ = 'matth'
|
__author__ = 'matth'
|
||||||
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from builtins import range
|
|
||||||
import unittest
|
|
||||||
import logging
|
import logging
|
||||||
from SpiffWorkflow.task import TaskState
|
from SpiffWorkflow.task import TaskState
|
||||||
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
|
|
||||||
from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase
|
from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase
|
||||||
|
|
||||||
__author__ = 'matth'
|
__author__ = 'matth'
|
||||||
@ -28,13 +25,9 @@ class BaseParallelTestCase(BpmnWorkflowTestCase):
|
|||||||
"Doing step '%s' (with choice='%s')", s, choice)
|
"Doing step '%s' (with choice='%s')", s, choice)
|
||||||
else:
|
else:
|
||||||
logging.info("Doing step '%s'", s)
|
logging.info("Doing step '%s'", s)
|
||||||
# logging.debug(self.workflow.get_dump())
|
self.do_next_named_step(s, choice=choice, only_one_instance=only_one_instance)
|
||||||
self.do_next_named_step(
|
|
||||||
s, choice=choice, only_one_instance=only_one_instance)
|
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
if save_restore:
|
if save_restore:
|
||||||
# logging.debug("Before SaveRestore: \n%s" %
|
|
||||||
# self.workflow.get_dump())
|
|
||||||
self.save_restore()
|
self.save_restore()
|
||||||
|
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
|
@ -34,7 +34,7 @@ class TestUserTask(UserTask):
|
|||||||
|
|
||||||
def do_choice(self, task, choice):
|
def do_choice(self, task, choice):
|
||||||
task.set_data(choice=choice)
|
task.set_data(choice=choice)
|
||||||
task.complete()
|
task.run()
|
||||||
|
|
||||||
|
|
||||||
class TestExclusiveGatewayParser(ConditionalGatewayParser):
|
class TestExclusiveGatewayParser(ConditionalGatewayParser):
|
||||||
|
@ -64,7 +64,7 @@ class BpmnWorkflowTestCase(unittest.TestCase):
|
|||||||
def switch_workflow(p):
|
def switch_workflow(p):
|
||||||
for task_id, sp in p.workflow._get_outermost_workflow().subprocesses.items():
|
for task_id, sp in p.workflow._get_outermost_workflow().subprocesses.items():
|
||||||
if p in sp.get_tasks(workflow=sp):
|
if p in sp.get_tasks(workflow=sp):
|
||||||
return p.workflow.get_task(task_id)
|
return p.workflow.get_task_from_id(task_id)
|
||||||
|
|
||||||
def is_match(t):
|
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.description == step_name_path[-1]):
|
||||||
@ -116,7 +116,7 @@ class BpmnWorkflowTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
if set_attribs:
|
if set_attribs:
|
||||||
tasks[0].set_data(**set_attribs)
|
tasks[0].set_data(**set_attribs)
|
||||||
tasks[0].complete()
|
tasks[0].run()
|
||||||
|
|
||||||
def save_restore(self):
|
def save_restore(self):
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine
|
|||||||
|
|
||||||
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
|
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
|
||||||
from SpiffWorkflow.exceptions import WorkflowTaskException
|
from SpiffWorkflow.exceptions import WorkflowTaskException
|
||||||
from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase
|
from .BpmnWorkflowTestCase import BpmnWorkflowTestCase
|
||||||
|
|
||||||
__author__ = 'kellym'
|
__author__ = 'kellym'
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ class CollaborationTest(BpmnWorkflowTestCase):
|
|||||||
workflow.do_engine_steps()
|
workflow.do_engine_steps()
|
||||||
for idx, task in enumerate(workflow.get_ready_user_tasks()):
|
for idx, task in enumerate(workflow.get_ready_user_tasks()):
|
||||||
task.data['task_num'] = idx
|
task.data['task_num'] = idx
|
||||||
task.complete()
|
task.run()
|
||||||
workflow.do_engine_steps()
|
workflow.do_engine_steps()
|
||||||
ready_tasks = workflow.get_ready_user_tasks()
|
ready_tasks = workflow.get_ready_user_tasks()
|
||||||
waiting = workflow.get_tasks_from_spec_name('get_response')
|
waiting = workflow.get_tasks_from_spec_name('get_response')
|
||||||
@ -94,7 +94,7 @@ class CollaborationTest(BpmnWorkflowTestCase):
|
|||||||
# Now copy the task_num that was sent into a new variable
|
# Now copy the task_num that was sent into a new variable
|
||||||
for task in ready_tasks:
|
for task in ready_tasks:
|
||||||
task.data.update(init_id=task.data['task_num'])
|
task.data.update(init_id=task.data['task_num'])
|
||||||
task.complete()
|
task.run()
|
||||||
workflow.do_engine_steps()
|
workflow.do_engine_steps()
|
||||||
# If the messages were routed properly, the id should match
|
# If the messages were routed properly, the id should match
|
||||||
for task in workflow.get_tasks_from_spec_name('subprocess_end'):
|
for task in workflow.get_tasks_from_spec_name('subprocess_end'):
|
||||||
@ -108,7 +108,7 @@ class CollaborationTest(BpmnWorkflowTestCase):
|
|||||||
workflow.do_engine_steps()
|
workflow.do_engine_steps()
|
||||||
for idx, task in enumerate(workflow.get_ready_user_tasks()):
|
for idx, task in enumerate(workflow.get_ready_user_tasks()):
|
||||||
task.data['task_num'] = idx
|
task.data['task_num'] = idx
|
||||||
task.complete()
|
task.run()
|
||||||
workflow.do_engine_steps()
|
workflow.do_engine_steps()
|
||||||
|
|
||||||
# Two processes should have been started and two corresponding catch events should be waiting
|
# Two processes should have been started and two corresponding catch events should be waiting
|
||||||
@ -121,12 +121,12 @@ class CollaborationTest(BpmnWorkflowTestCase):
|
|||||||
# Now copy the task_num that was sent into a new variable
|
# Now copy the task_num that was sent into a new variable
|
||||||
for task in ready_tasks:
|
for task in ready_tasks:
|
||||||
task.data.update(init_id=task.data['task_num'])
|
task.data.update(init_id=task.data['task_num'])
|
||||||
task.complete()
|
task.run()
|
||||||
workflow.do_engine_steps()
|
workflow.do_engine_steps()
|
||||||
|
|
||||||
# Complete dummy tasks
|
# Complete dummy tasks
|
||||||
for task in workflow.get_ready_user_tasks():
|
for task in workflow.get_ready_user_tasks():
|
||||||
task.complete()
|
task.run()
|
||||||
workflow.do_engine_steps()
|
workflow.do_engine_steps()
|
||||||
|
|
||||||
# Repeat for the other process, using a different mapped name
|
# Repeat for the other process, using a different mapped name
|
||||||
@ -136,7 +136,7 @@ class CollaborationTest(BpmnWorkflowTestCase):
|
|||||||
self.assertEqual(len(waiting), 2)
|
self.assertEqual(len(waiting), 2)
|
||||||
for task in ready_tasks:
|
for task in ready_tasks:
|
||||||
task.data.update(subprocess=task.data['task_num'])
|
task.data.update(subprocess=task.data['task_num'])
|
||||||
task.complete()
|
task.run()
|
||||||
workflow.do_engine_steps()
|
workflow.do_engine_steps()
|
||||||
|
|
||||||
# If the messages were routed properly, the id should match
|
# If the messages were routed properly, the id should match
|
||||||
|
@ -22,13 +22,13 @@ class DataObjectReferenceTest(BpmnWorkflowTestCase):
|
|||||||
# Add the data so that we can advance the workflow
|
# Add the data so that we can advance the workflow
|
||||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||||
ready_tasks[0].data = { 'obj_1': 'hello' }
|
ready_tasks[0].data = { 'obj_1': 'hello' }
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
|
|
||||||
# Remove the data before advancing
|
# Remove the data before advancing
|
||||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||||
self.workflow.data.pop('obj_1')
|
self.workflow.data.pop('obj_1')
|
||||||
with self.assertRaises(WorkflowDataException) as exc:
|
with self.assertRaises(WorkflowDataException) as exc:
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
self.assertEqual(exc.data_output.name, 'obj_1')
|
self.assertEqual(exc.data_output.name, 'obj_1')
|
||||||
|
|
||||||
def testMissingDataOutput(self):
|
def testMissingDataOutput(self):
|
||||||
@ -37,7 +37,7 @@ class DataObjectReferenceTest(BpmnWorkflowTestCase):
|
|||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||||
with self.assertRaises(WorkflowDataException) as exc:
|
with self.assertRaises(WorkflowDataException) as exc:
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
self.assertEqual(exc.data_output.name, 'obj_1')
|
self.assertEqual(exc.data_output.name, 'obj_1')
|
||||||
|
|
||||||
def actual_test(self, save_restore):
|
def actual_test(self, save_restore):
|
||||||
@ -48,7 +48,7 @@ class DataObjectReferenceTest(BpmnWorkflowTestCase):
|
|||||||
# Set up the data
|
# Set up the data
|
||||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||||
ready_tasks[0].data = { 'obj_1': 'hello' }
|
ready_tasks[0].data = { 'obj_1': 'hello' }
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
# After task completion, obj_1 should be copied out of the task into the workflow
|
# After task completion, obj_1 should be copied out of the task into the workflow
|
||||||
self.assertNotIn('obj_1', ready_tasks[0].data)
|
self.assertNotIn('obj_1', ready_tasks[0].data)
|
||||||
self.assertIn('obj_1', self.workflow.data)
|
self.assertIn('obj_1', self.workflow.data)
|
||||||
@ -59,14 +59,14 @@ class DataObjectReferenceTest(BpmnWorkflowTestCase):
|
|||||||
# Set a value for obj_1 in the task data again
|
# Set a value for obj_1 in the task data again
|
||||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||||
ready_tasks[0].data = { 'obj_1': 'hello again' }
|
ready_tasks[0].data = { 'obj_1': 'hello again' }
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
|
|
||||||
# Check to make sure we use the workflow value instead of the value we set
|
# Check to make sure we use the workflow value instead of the value we set
|
||||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||||
self.assertEqual(ready_tasks[0].data['obj_1'], 'hello')
|
self.assertEqual(ready_tasks[0].data['obj_1'], 'hello')
|
||||||
# Modify the value in the task
|
# Modify the value in the task
|
||||||
ready_tasks[0].data = { 'obj_1': 'hello again' }
|
ready_tasks[0].data = { 'obj_1': 'hello again' }
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
# We did not set an output data reference so obj_1 should remain unchanged in the workflow data
|
# We did not set an output data reference so obj_1 should remain unchanged in the workflow data
|
||||||
# and be removed from the task data
|
# and be removed from the task data
|
||||||
self.assertNotIn('obj_1', ready_tasks[0].data)
|
self.assertNotIn('obj_1', ready_tasks[0].data)
|
||||||
@ -77,7 +77,7 @@ class DataObjectReferenceTest(BpmnWorkflowTestCase):
|
|||||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||||
self.assertEqual(ready_tasks[0].data['obj_1'], 'hello')
|
self.assertEqual(ready_tasks[0].data['obj_1'], 'hello')
|
||||||
ready_tasks[0].data['obj_1'] = 'hello again'
|
ready_tasks[0].data['obj_1'] = 'hello again'
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
sp = self.workflow.get_tasks_from_spec_name('subprocess')[0]
|
sp = self.workflow.get_tasks_from_spec_name('subprocess')[0]
|
||||||
# It was copied out
|
# It was copied out
|
||||||
|
@ -76,7 +76,7 @@ class CallActivityDataTest(BpmnWorkflowTestCase):
|
|||||||
waiting = self.workflow.get_tasks(TaskState.WAITING)
|
waiting = self.workflow.get_tasks(TaskState.WAITING)
|
||||||
while len(waiting) == 0:
|
while len(waiting) == 0:
|
||||||
next_task = self.workflow.get_tasks(TaskState.READY)[0]
|
next_task = self.workflow.get_tasks(TaskState.READY)[0]
|
||||||
next_task.complete()
|
next_task.run()
|
||||||
waiting = self.workflow.get_tasks(TaskState.WAITING)
|
waiting = self.workflow.get_tasks(TaskState.WAITING)
|
||||||
|
|
||||||
def complete_subprocess(self):
|
def complete_subprocess(self):
|
||||||
@ -84,7 +84,7 @@ class CallActivityDataTest(BpmnWorkflowTestCase):
|
|||||||
waiting = self.workflow.get_tasks(TaskState.WAITING)
|
waiting = self.workflow.get_tasks(TaskState.WAITING)
|
||||||
while len(waiting) > 0:
|
while len(waiting) > 0:
|
||||||
next_task = self.workflow.get_tasks(TaskState.READY)[0]
|
next_task = self.workflow.get_tasks(TaskState.READY)[0]
|
||||||
next_task.complete()
|
next_task.run()
|
||||||
waiting = self.workflow.get_tasks(TaskState.WAITING)
|
waiting = self.workflow.get_tasks(TaskState.WAITING)
|
||||||
|
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ class IOSpecOnTaskTest(BpmnWorkflowTestCase):
|
|||||||
task = self.workflow.get_tasks_from_spec_name('any_task')[0]
|
task = self.workflow.get_tasks_from_spec_name('any_task')[0]
|
||||||
task.data.update({'out_1': 1})
|
task.data.update({'out_1': 1})
|
||||||
with self.assertRaises(WorkflowDataException) as exc:
|
with self.assertRaises(WorkflowDataException) as exc:
|
||||||
task.complete()
|
task.run()
|
||||||
self.assertEqual(exc.exception.data_output.name, 'out_2')
|
self.assertEqual(exc.exception.data_output.name, 'out_2')
|
||||||
|
|
||||||
def actual_test(self, save_restore=False):
|
def actual_test(self, save_restore=False):
|
||||||
@ -124,6 +124,6 @@ class IOSpecOnTaskTest(BpmnWorkflowTestCase):
|
|||||||
task = self.workflow.get_tasks_from_spec_name('any_task')[0]
|
task = self.workflow.get_tasks_from_spec_name('any_task')[0]
|
||||||
self.assertDictEqual(task.data, {'in_1': 1, 'in_2': 'hello world'})
|
self.assertDictEqual(task.data, {'in_1': 1, 'in_2': 'hello world'})
|
||||||
task.data.update({'out_1': 1, 'out_2': 'bye', 'extra': True})
|
task.data.update({'out_1': 1, 'out_2': 'bye', 'extra': True})
|
||||||
task.complete()
|
task.run()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.assertDictEqual(self.workflow.last_task.data, {'out_1': 1, 'out_2': 'bye'})
|
self.assertDictEqual(self.workflow.last_task.data, {'out_1': 1, 'out_2': 'bye'})
|
||||||
|
@ -36,4 +36,4 @@ class InclusiveGatewayTest(BpmnWorkflowTestCase):
|
|||||||
def set_data(self, value):
|
def set_data(self, value):
|
||||||
task = self.workflow.get_ready_user_tasks()[0]
|
task = self.workflow.get_ready_user_tasks()[0]
|
||||||
task.data = value
|
task.data = value
|
||||||
task.complete()
|
task.run()
|
||||||
|
@ -26,7 +26,7 @@ class BaseTestCase(BpmnWorkflowTestCase):
|
|||||||
self.assertEqual(task.task_spec.name, 'any_task [child]')
|
self.assertEqual(task.task_spec.name, 'any_task [child]')
|
||||||
self.assertIn('input_item', task.data)
|
self.assertIn('input_item', task.data)
|
||||||
task.data['output_item'] = task.data['input_item'] * 2
|
task.data['output_item'] = task.data['input_item'] * 2
|
||||||
task.complete()
|
task.run()
|
||||||
if save_restore:
|
if save_restore:
|
||||||
self.save_restore()
|
self.save_restore()
|
||||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||||
@ -47,7 +47,7 @@ class BaseTestCase(BpmnWorkflowTestCase):
|
|||||||
self.assertEqual(len(ready_tasks), 3)
|
self.assertEqual(len(ready_tasks), 3)
|
||||||
task = [t for t in ready_tasks if t.data['input_item'] == 2][0]
|
task = [t for t in ready_tasks if t.data['input_item'] == 2][0]
|
||||||
task.data['output_item'] = task.data['input_item'] * 2
|
task.data['output_item'] = task.data['input_item'] * 2
|
||||||
task.complete()
|
task.run()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.workflow.refresh_waiting_tasks()
|
self.workflow.refresh_waiting_tasks()
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ class ParallelOrderTest(BpmnWorkflowTestCase):
|
|||||||
|
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.assertFalse(self.workflow.is_completed())
|
self.assertFalse(self.workflow.is_completed())
|
||||||
self.assertEquals(4, len(self.workflow.get_ready_user_tasks()))
|
self.assertEqual(4, len(self.workflow.get_ready_user_tasks()))
|
||||||
tasks = self.workflow.get_ready_user_tasks()
|
tasks = self.workflow.get_ready_user_tasks()
|
||||||
self.assertEquals("Task 1", tasks[0].get_description())
|
self.assertEquals("Task 1", tasks[0].get_description())
|
||||||
self.assertEquals("Task 2", tasks[1].get_description())
|
self.assertEquals("Task 2", tasks[1].get_description())
|
||||||
|
@ -51,7 +51,7 @@ class ParallelThroughSameTaskTest(BpmnWorkflowTestCase):
|
|||||||
self.assertEqual(2, len(ready_tasks))
|
self.assertEqual(2, len(ready_tasks))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'Repeated Task', ready_tasks[0].task_spec.description)
|
'Repeated Task', ready_tasks[0].task_spec.description)
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
# The inclusive gateway allows us through here, because there is no route for the other thread
|
# The inclusive gateway allows us through here, because there is no route for the other thread
|
||||||
# that doesn't use the same sequence flow
|
# that doesn't use the same sequence flow
|
||||||
@ -82,7 +82,7 @@ class ParallelThroughSameTaskTest(BpmnWorkflowTestCase):
|
|||||||
self.assertEqual(2, len(ready_tasks))
|
self.assertEqual(2, len(ready_tasks))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'Repeated Task', ready_tasks[0].task_spec.description)
|
'Repeated Task', ready_tasks[0].task_spec.description)
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.save_restore()
|
self.save_restore()
|
||||||
# The inclusive gateway allows us through here, because there is no route for the other thread
|
# The inclusive gateway allows us through here, because there is no route for the other thread
|
||||||
|
@ -35,7 +35,7 @@ class ResetSubProcessTest(BpmnWorkflowTestCase):
|
|||||||
|
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
top_level_task = self.workflow.get_ready_user_tasks()[0]
|
top_level_task = self.workflow.get_ready_user_tasks()[0]
|
||||||
self.workflow.complete_task_from_id(top_level_task.id)
|
self.workflow.run_task_from_id(top_level_task.id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
task = self.workflow.get_ready_user_tasks()[0]
|
task = self.workflow.get_ready_user_tasks()[0]
|
||||||
self.save_restore()
|
self.save_restore()
|
||||||
@ -50,11 +50,11 @@ class ResetSubProcessTest(BpmnWorkflowTestCase):
|
|||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.assertEqual(1, len(self.workflow.get_ready_user_tasks()))
|
self.assertEqual(1, len(self.workflow.get_ready_user_tasks()))
|
||||||
task = self.workflow.get_ready_user_tasks()[0]
|
task = self.workflow.get_ready_user_tasks()[0]
|
||||||
self.workflow.complete_task_from_id(task.id)
|
self.workflow.run_task_from_id(task.id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
task = self.workflow.get_ready_user_tasks()[0]
|
task = self.workflow.get_ready_user_tasks()[0]
|
||||||
self.assertEqual(task.get_name(),'SubTask2')
|
self.assertEqual(task.get_name(),'SubTask2')
|
||||||
self.workflow.complete_task_from_id(task.id)
|
self.workflow.run_task_from_id(task.id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
task = self.workflow.get_tasks_from_spec_name('Task1')[0]
|
task = self.workflow.get_tasks_from_spec_name('Task1')[0]
|
||||||
task.reset_token(self.workflow.last_task.data)
|
task.reset_token(self.workflow.last_task.data)
|
||||||
@ -62,19 +62,19 @@ class ResetSubProcessTest(BpmnWorkflowTestCase):
|
|||||||
self.reload_save_restore()
|
self.reload_save_restore()
|
||||||
task = self.workflow.get_ready_user_tasks()[0]
|
task = self.workflow.get_ready_user_tasks()[0]
|
||||||
self.assertEqual(task.get_name(),'Task1')
|
self.assertEqual(task.get_name(),'Task1')
|
||||||
self.workflow.complete_task_from_id(task.id)
|
self.workflow.run_task_from_id(task.id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
task = self.workflow.get_ready_user_tasks()[0]
|
task = self.workflow.get_ready_user_tasks()[0]
|
||||||
self.assertEqual(task.get_name(),'Subtask2')
|
self.assertEqual(task.get_name(),'Subtask2')
|
||||||
self.workflow.complete_task_from_id(task.id)
|
self.workflow.run_task_from_id(task.id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
task = self.workflow.get_ready_user_tasks()[0]
|
task = self.workflow.get_ready_user_tasks()[0]
|
||||||
self.assertEqual(task.get_name(),'Subtask2A')
|
self.assertEqual(task.get_name(),'Subtask2A')
|
||||||
self.workflow.complete_task_from_id(task.id)
|
self.workflow.run_task_from_id(task.id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
task = self.workflow.get_ready_user_tasks()[0]
|
task = self.workflow.get_ready_user_tasks()[0]
|
||||||
self.assertEqual(task.get_name(),'Task2')
|
self.assertEqual(task.get_name(),'Task2')
|
||||||
self.workflow.complete_task_from_id(task.id)
|
self.workflow.run_task_from_id(task.id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.assertTrue(self.workflow.is_completed())
|
self.assertTrue(self.workflow.is_completed())
|
||||||
|
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
|
|
||||||
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
|
|
||||||
from SpiffWorkflow.task import TaskState
|
|
||||||
|
|
||||||
from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase
|
|
||||||
|
|
||||||
class ResetTimerTest(BpmnWorkflowTestCase):
|
|
||||||
|
|
||||||
def test_timer(self):
|
|
||||||
spec, subprocess = self.load_workflow_spec('reset_timer.bpmn', 'main')
|
|
||||||
self.workflow = BpmnWorkflow(spec, subprocess)
|
|
||||||
self.workflow.do_engine_steps()
|
|
||||||
task_1 = self.workflow.get_tasks_from_spec_name('task_1')[0]
|
|
||||||
timer = self.workflow.get_tasks_from_spec_name('timer')[0]
|
|
||||||
original_timer = timer.internal_data.get('event_value')
|
|
||||||
# This returns us to the task
|
|
||||||
task_1.data['modify'] = True
|
|
||||||
task_1.complete()
|
|
||||||
self.workflow.do_engine_steps()
|
|
||||||
# The timer should be waiting and the time should have been updated
|
|
||||||
self.assertEqual(task_1.state, TaskState.READY)
|
|
||||||
self.assertEqual(timer.state, TaskState.WAITING)
|
|
||||||
self.assertGreater(timer.internal_data.get('event_value'), original_timer)
|
|
||||||
task_1.data['modify'] = False
|
|
||||||
task_1.complete()
|
|
||||||
self.workflow.do_engine_steps()
|
|
||||||
self.assertEqual(timer.state, TaskState.CANCELLED)
|
|
||||||
self.assertTrue(self.workflow.is_completed())
|
|
@ -27,7 +27,7 @@ class BaseTestCase(BpmnWorkflowTestCase):
|
|||||||
self.assertEqual(task.task_spec.name, 'any_task [child]')
|
self.assertEqual(task.task_spec.name, 'any_task [child]')
|
||||||
self.assertIn('input_item', task.data)
|
self.assertIn('input_item', task.data)
|
||||||
task.data['output_item'] = task.data['input_item'] * 2
|
task.data['output_item'] = task.data['input_item'] * 2
|
||||||
task.complete()
|
task.run()
|
||||||
if save_restore:
|
if save_restore:
|
||||||
self.save_restore()
|
self.save_restore()
|
||||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||||
@ -54,7 +54,7 @@ class BaseTestCase(BpmnWorkflowTestCase):
|
|||||||
self.assertEqual(ready.task_spec.name, 'any_task [child]')
|
self.assertEqual(ready.task_spec.name, 'any_task [child]')
|
||||||
self.assertIn('input_item', ready.data)
|
self.assertIn('input_item', ready.data)
|
||||||
ready.data['output_item'] = ready.data['input_item'] * 2
|
ready.data['output_item'] = ready.data['input_item'] * 2
|
||||||
ready.complete()
|
ready.run()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.workflow.refresh_waiting_tasks()
|
self.workflow.refresh_waiting_tasks()
|
||||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||||
|
@ -21,7 +21,7 @@ class StandardLoopTest(BpmnWorkflowTestCase):
|
|||||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||||
self.assertEqual(len(ready_tasks), 1)
|
self.assertEqual(len(ready_tasks), 1)
|
||||||
ready_tasks[0].data[str(idx)] = True
|
ready_tasks[0].data[str(idx)] = True
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
|
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.assertTrue(self.workflow.is_completed())
|
self.assertTrue(self.workflow.is_completed())
|
||||||
@ -36,7 +36,7 @@ class StandardLoopTest(BpmnWorkflowTestCase):
|
|||||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||||
self.assertEqual(len(ready_tasks), 1)
|
self.assertEqual(len(ready_tasks), 1)
|
||||||
ready_tasks[0].data['done'] = True
|
ready_tasks[0].data['done'] = True
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
|
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.assertTrue(self.workflow.is_completed())
|
self.assertTrue(self.workflow.is_completed())
|
||||||
|
@ -35,7 +35,7 @@ class SwimLaneTest(BpmnWorkflowTestCase):
|
|||||||
self.assertEqual(0, len(btasks))
|
self.assertEqual(0, len(btasks))
|
||||||
task = atasks[0]
|
task = atasks[0]
|
||||||
self.assertEqual('Activity_A1', task.task_spec.name)
|
self.assertEqual('Activity_A1', task.task_spec.name)
|
||||||
self.workflow.complete_task_from_id(task.id)
|
self.workflow.run_task_from_id(task.id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
atasks = self.workflow.get_ready_user_tasks(lane="A")
|
atasks = self.workflow.get_ready_user_tasks(lane="A")
|
||||||
btasks = self.workflow.get_ready_user_tasks(lane="B")
|
btasks = self.workflow.get_ready_user_tasks(lane="B")
|
||||||
@ -44,10 +44,10 @@ class SwimLaneTest(BpmnWorkflowTestCase):
|
|||||||
|
|
||||||
# Complete the gateway and the two tasks in B Lane
|
# Complete the gateway and the two tasks in B Lane
|
||||||
btasks[0].data = {'NeedClarification': False}
|
btasks[0].data = {'NeedClarification': False}
|
||||||
self.workflow.complete_task_from_id(btasks[0].id)
|
self.workflow.run_task_from_id(btasks[0].id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
btasks = self.workflow.get_ready_user_tasks(lane="B")
|
btasks = self.workflow.get_ready_user_tasks(lane="B")
|
||||||
self.workflow.complete_task_from_id(btasks[0].id)
|
self.workflow.run_task_from_id(btasks[0].id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
|
|
||||||
# Assert we are in lane C
|
# Assert we are in lane C
|
||||||
@ -56,7 +56,7 @@ class SwimLaneTest(BpmnWorkflowTestCase):
|
|||||||
self.assertEqual(tasks[0].task_spec.lane, "C")
|
self.assertEqual(tasks[0].task_spec.lane, "C")
|
||||||
|
|
||||||
# Step into the sub-process, assure that is also in lane C
|
# Step into the sub-process, assure that is also in lane C
|
||||||
self.workflow.complete_task_from_id(tasks[0].id)
|
self.workflow.run_task_from_id(tasks[0].id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
tasks = self.workflow.get_ready_user_tasks()
|
tasks = self.workflow.get_ready_user_tasks()
|
||||||
self.assertEqual("SubProcessTask", tasks[0].task_spec.description)
|
self.assertEqual("SubProcessTask", tasks[0].task_spec.description)
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
__author__ = 'sartography'
|
|
||||||
|
|
||||||
class CustomScriptEngine(PythonScriptEngine):
|
|
||||||
"""This is a custom script processor that can be easily injected into Spiff Workflow.
|
|
||||||
It will execute python code read in from the bpmn. It will also make any scripts in the
|
|
||||||
scripts directory available for execution. """
|
|
||||||
def __init__(self):
|
|
||||||
environment = TaskDataEnvironment({
|
|
||||||
'timedelta': datetime.timedelta,
|
|
||||||
})
|
|
||||||
super().__init__(environment=environment)
|
|
||||||
|
|
||||||
class TooManyLoopsTest(BpmnWorkflowTestCase):
|
|
||||||
|
|
||||||
"""Looping back around many times would cause the tree of tasks to grow
|
|
||||||
for each loop, doing this a 100 or 1000 times would cause the system to
|
|
||||||
run fail in various ways. This assures that is no longer the case."""
|
|
||||||
|
|
||||||
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):
|
|
||||||
spec, subprocesses = self.load_workflow_spec('too_many_loops*.bpmn', 'loops')
|
|
||||||
self.workflow = BpmnWorkflow(spec, subprocesses, script_engine=CustomScriptEngine())
|
|
||||||
counter = 0
|
|
||||||
data = {}
|
|
||||||
while not self.workflow.is_completed():
|
|
||||||
self.workflow.do_engine_steps()
|
|
||||||
self.workflow.refresh_waiting_tasks()
|
|
||||||
if (self.workflow.last_task.data != data):
|
|
||||||
data = self.workflow.last_task.data
|
|
||||||
counter += 1 # There is a 10 millisecond wait task.
|
|
||||||
if save_restore:
|
|
||||||
self.save_restore()
|
|
||||||
self.workflow.script_engine = CustomScriptEngine()
|
|
||||||
self.assertEqual(20, self.workflow.last_task.data['counter'])
|
|
||||||
|
|
||||||
def test_with_sub_process(self):
|
|
||||||
# Found an issue where looping back would fail when it happens
|
|
||||||
# right after a sub-process. So assuring this is fixed.
|
|
||||||
counter = 0
|
|
||||||
spec, subprocesses = self.load_workflow_spec('too_many_loops_sub_process.bpmn', 'loops_sub')
|
|
||||||
self.workflow = BpmnWorkflow(spec, subprocesses, script_engine=CustomScriptEngine())
|
|
||||||
data = {}
|
|
||||||
while not self.workflow.is_completed():
|
|
||||||
self.workflow.do_engine_steps()
|
|
||||||
self.workflow.refresh_waiting_tasks()
|
|
||||||
if (self.workflow.last_task.data != data):
|
|
||||||
data = self.workflow.last_task.data
|
|
||||||
counter += 1 # There is a 10 millisecond wait task.
|
|
||||||
# self.save_restore()
|
|
||||||
self.assertEqual(20, self.workflow.last_task.data['counter'])
|
|
||||||
# One less, because we don't go back through once the first counter
|
|
||||||
# hits 20.
|
|
||||||
self.assertEqual(19, self.workflow.last_task.data['counter2'])
|
|
||||||
|
|
||||||
def test_with_two_call_activities(self):
|
|
||||||
spec, subprocess = self.load_workflow_spec('sub_in_loop*.bpmn', 'main')
|
|
||||||
self.workflow = BpmnWorkflow(spec, subprocess, script_engine=CustomScriptEngine())
|
|
||||||
self.workflow.do_engine_steps()
|
|
||||||
for loop in range(3):
|
|
||||||
ready = self.workflow.get_ready_user_tasks()
|
|
||||||
ready[0].data = { 'done': True if loop == 3 else False }
|
|
||||||
ready[0].complete()
|
|
||||||
self.workflow.refresh_waiting_tasks()
|
|
||||||
self.workflow.do_engine_steps()
|
|
||||||
self.save_restore()
|
|
||||||
self.workflow.script_engine = CustomScriptEngine()
|
|
||||||
|
|
||||||
def suite():
|
|
||||||
return unittest.TestLoader().loadTestsFromTestCase(TooManyLoopsTest)
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.TextTestRunner(verbosity=2).run(suite())
|
|
@ -1,291 +1,303 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:signavio="http://www.signavio.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" exporter="Signavio Process Editor, http://www.signavio.com" exporterVersion="" expressionLanguage="http://www.w3.org/1999/XPath" id="sid-fafc406d-50f2-41f2-b75b-ad652445d52e" targetNamespace="http://www.signavio.com/bpmn20" typeLanguage="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL http://www.omg.org/spec/BPMN/2.0/20100501/BPMN20.xsd">
|
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:signavio="http://www.signavio.com" id="sid-fafc406d-50f2-41f2-b75b-ad652445d52e" targetNamespace="http://www.signavio.com/bpmn20" exporter="Camunda Modeler" exporterVersion="4.11.1" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL http://www.omg.org/spec/BPMN/2.0/20100501/BPMN20.xsd">
|
||||||
<collaboration id="sid-5801c79c-823f-4040-b680-417ef5bcb3a2">
|
<collaboration id="sid-5801c79c-823f-4040-b680-417ef5bcb3a2">
|
||||||
<participant id="sid-B2E5AD50-035A-4CE8-B8A3-B175A6767B00" name="Parallel Looping After Join" processRef="sid-41eb2b6c-08bc-4a61-b38b-5f32052139c5">
|
<participant id="sid-B2E5AD50-035A-4CE8-B8A3-B175A6767B00" name="Parallel Looping After Join" processRef="sid-41eb2b6c-08bc-4a61-b38b-5f32052139c5">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff"/>
|
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
</participant>
|
</participant>
|
||||||
</collaboration>
|
</collaboration>
|
||||||
<process id="sid-41eb2b6c-08bc-4a61-b38b-5f32052139c5" isClosed="false" isExecutable="false" name="Parallel Looping After Join" processType="None">
|
<process id="sid-41eb2b6c-08bc-4a61-b38b-5f32052139c5" name="Parallel Looping After Join" processType="None" isClosed="false" isExecutable="false">
|
||||||
<laneSet id="sid-127ca06b-aba8-45aa-bd8a-7be70ec94b2c">
|
<laneSet id="sid-127ca06b-aba8-45aa-bd8a-7be70ec94b2c">
|
||||||
<lane id="sid-72009BCE-46B4-4B4B-AEAE-1E7199522142" name="Tester">
|
<lane id="sid-72009BCE-46B4-4B4B-AEAE-1E7199522142" name="Tester">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue=""/>
|
<signavio:signavioMetaData metaKey="bgcolor" metaValue="" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
<flowNodeRef>sid-B33EE043-AB93-4343-A1D4-7B267E2DAFBE</flowNodeRef>
|
<flowNodeRef>start</flowNodeRef>
|
||||||
<flowNodeRef>sid-349F8C0C-45EA-489C-84DD-1D944F48D778</flowNodeRef>
|
<flowNodeRef>first_split</flowNodeRef>
|
||||||
<flowNodeRef>sid-57463471-693A-42A2-9EC6-6460BEDECA86</flowNodeRef>
|
<flowNodeRef>one</flowNodeRef>
|
||||||
<flowNodeRef>sid-CA089240-802A-4C32-9130-FB1A33DDCCC3</flowNodeRef>
|
<flowNodeRef>two</flowNodeRef>
|
||||||
<flowNodeRef>sid-E976FBC2-266E-420F-8D4D-C8FBC6199090</flowNodeRef>
|
<flowNodeRef>join_of_first</flowNodeRef>
|
||||||
<flowNodeRef>sid-F3A979E3-F586-4807-8223-1FAB5A5647B0</flowNodeRef>
|
<flowNodeRef>retry</flowNodeRef>
|
||||||
<flowNodeRef>sid-51816945-79BF-47F9-BA3C-E95ABAE3D1DB</flowNodeRef>
|
<flowNodeRef>end</flowNodeRef>
|
||||||
<flowNodeRef>sid-4F7F3AA6-4E8E-458D-BBEF-E03FC0646897</flowNodeRef>
|
<flowNodeRef>second_split</flowNodeRef>
|
||||||
<flowNodeRef>sid-ABD788A3-CD57-4280-A22A-260B3AEEE138</flowNodeRef>
|
<flowNodeRef>two_a</flowNodeRef>
|
||||||
<flowNodeRef>sid-E7B8898A-0D14-4E98-B3D7-736B94EE3FA7</flowNodeRef>
|
<flowNodeRef>two_b</flowNodeRef>
|
||||||
<flowNodeRef>sid-A1609BD5-1E4A-47AE-8648-1DD41D1B1D58</flowNodeRef>
|
<flowNodeRef>join_of_second_split</flowNodeRef>
|
||||||
<flowNodeRef>sid-1946C635-7886-4687-844F-C644FA6222B8</flowNodeRef>
|
<flowNodeRef>two_done</flowNodeRef>
|
||||||
<flowNodeRef>sid-4C3B3C16-91DB-43E3-A816-FFEE572E61E1</flowNodeRef>
|
<flowNodeRef>exclusive</flowNodeRef>
|
||||||
<flowNodeRef>sid-55C018B8-C073-4292-9ED0-79BDE50E7498</flowNodeRef>
|
<flowNodeRef>done</flowNodeRef>
|
||||||
<flowNodeRef>sid-E7904BFA-1F17-478E-91C9-C8A5B64190C9</flowNodeRef>
|
<flowNodeRef>go</flowNodeRef>
|
||||||
</lane>
|
</lane>
|
||||||
</laneSet>
|
</laneSet>
|
||||||
<startEvent id="sid-B33EE043-AB93-4343-A1D4-7B267E2DAFBE" name="">
|
<startEvent id="start" name="">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff"/>
|
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
<outgoing>sid-F3994F51-FE54-4910-A1F4-E5895AA1A612</outgoing>
|
<outgoing>sid-F3994F51-FE54-4910-A1F4-E5895AA1A612</outgoing>
|
||||||
</startEvent>
|
</startEvent>
|
||||||
<parallelGateway gatewayDirection="Diverging" id="sid-349F8C0C-45EA-489C-84DD-1D944F48D778" name="First Split">
|
<parallelGateway id="first_split" name="First Split" gatewayDirection="Diverging">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff"/>
|
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
<incoming>sid-3E0EBE59-75C8-465C-90CC-197CE808A96E</incoming>
|
<incoming>sid-3E0EBE59-75C8-465C-90CC-197CE808A96E</incoming>
|
||||||
<outgoing>sid-7E15C71B-DE9E-4788-B140-A647C99FDC94</outgoing>
|
<outgoing>sid-7E15C71B-DE9E-4788-B140-A647C99FDC94</outgoing>
|
||||||
<outgoing>sid-B6E22A74-A691-453A-A789-B9F8AF787D7C</outgoing>
|
<outgoing>sid-B6E22A74-A691-453A-A789-B9F8AF787D7C</outgoing>
|
||||||
</parallelGateway>
|
</parallelGateway>
|
||||||
<task completionQuantity="1" id="sid-57463471-693A-42A2-9EC6-6460BEDECA86" isForCompensation="false" name="1" startQuantity="1">
|
<task id="one" name="1">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc"/>
|
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
<incoming>sid-7E15C71B-DE9E-4788-B140-A647C99FDC94</incoming>
|
<incoming>sid-7E15C71B-DE9E-4788-B140-A647C99FDC94</incoming>
|
||||||
<outgoing>sid-607CB05E-8762-41B6-AD43-C3970661A99D</outgoing>
|
<outgoing>join_of_first_split</outgoing>
|
||||||
</task>
|
</task>
|
||||||
<task completionQuantity="1" id="sid-CA089240-802A-4C32-9130-FB1A33DDCCC3" isForCompensation="false" name="2" startQuantity="1">
|
<task id="two" name="2">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc"/>
|
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
<incoming>sid-B6E22A74-A691-453A-A789-B9F8AF787D7C</incoming>
|
<incoming>sid-B6E22A74-A691-453A-A789-B9F8AF787D7C</incoming>
|
||||||
<outgoing>sid-CAEAD081-6E73-4C98-8656-C67DA18F5140</outgoing>
|
<outgoing>sid-CAEAD081-6E73-4C98-8656-C67DA18F5140</outgoing>
|
||||||
</task>
|
</task>
|
||||||
<parallelGateway gatewayDirection="Converging" id="sid-E976FBC2-266E-420F-8D4D-C8FBC6199090" name="Join of First Split">
|
<parallelGateway id="join_of_first" name="Join of First Split" gatewayDirection="Converging">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff"/>
|
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
<incoming>sid-231F8A51-752F-4CB3-8FD1-23D153238344</incoming>
|
<incoming>sid-231F8A51-752F-4CB3-8FD1-23D153238344</incoming>
|
||||||
<incoming>sid-607CB05E-8762-41B6-AD43-C3970661A99D</incoming>
|
<incoming>join_of_first_split</incoming>
|
||||||
<outgoing>sid-0895E09C-077C-4D12-8C11-31F28CBC7740</outgoing>
|
<outgoing>sid-0895E09C-077C-4D12-8C11-31F28CBC7740</outgoing>
|
||||||
</parallelGateway>
|
</parallelGateway>
|
||||||
<task completionQuantity="1" id="sid-F3A979E3-F586-4807-8223-1FAB5A5647B0" isForCompensation="false" name="Retry?" startQuantity="1">
|
<task id="retry" name="Retry?">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc"/>
|
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
<incoming>sid-0895E09C-077C-4D12-8C11-31F28CBC7740</incoming>
|
<incoming>sid-0895E09C-077C-4D12-8C11-31F28CBC7740</incoming>
|
||||||
<outgoing>sid-2668AC98-39E4-4B12-9052-930528086CAC</outgoing>
|
<outgoing>sid-2668AC98-39E4-4B12-9052-930528086CAC</outgoing>
|
||||||
</task>
|
</task>
|
||||||
<endEvent id="sid-51816945-79BF-47F9-BA3C-E95ABAE3D1DB" name="">
|
<endEvent id="end" name="">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff"/>
|
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
<incoming>sid-F6160C0E-216C-4D72-98D1-CC5549327D55</incoming>
|
<incoming>sid-F6160C0E-216C-4D72-98D1-CC5549327D55</incoming>
|
||||||
</endEvent>
|
</endEvent>
|
||||||
<parallelGateway gatewayDirection="Diverging" id="sid-4F7F3AA6-4E8E-458D-BBEF-E03FC0646897" name="Second Split">
|
<parallelGateway id="second_split" name="Second Split" gatewayDirection="Diverging">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff"/>
|
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
<incoming>sid-CAEAD081-6E73-4C98-8656-C67DA18F5140</incoming>
|
<incoming>sid-CAEAD081-6E73-4C98-8656-C67DA18F5140</incoming>
|
||||||
<outgoing>sid-918C653D-0960-4223-9C28-78114F238BCC</outgoing>
|
<outgoing>sid-918C653D-0960-4223-9C28-78114F238BCC</outgoing>
|
||||||
<outgoing>sid-FD82C2A6-7C54-4890-901E-A7E864F7605C</outgoing>
|
<outgoing>sid-FD82C2A6-7C54-4890-901E-A7E864F7605C</outgoing>
|
||||||
</parallelGateway>
|
</parallelGateway>
|
||||||
<task completionQuantity="1" id="sid-ABD788A3-CD57-4280-A22A-260B3AEEE138" isForCompensation="false" name="2A" startQuantity="1">
|
<task id="two_a" name="2A">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc"/>
|
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
<incoming>sid-918C653D-0960-4223-9C28-78114F238BCC</incoming>
|
<incoming>sid-918C653D-0960-4223-9C28-78114F238BCC</incoming>
|
||||||
<outgoing>sid-961AF51C-9935-410E-AAA4-105B19186F5E</outgoing>
|
<outgoing>sid-961AF51C-9935-410E-AAA4-105B19186F5E</outgoing>
|
||||||
</task>
|
</task>
|
||||||
<task completionQuantity="1" id="sid-E7B8898A-0D14-4E98-B3D7-736B94EE3FA7" isForCompensation="false" name="2B" startQuantity="1">
|
<task id="two_b" name="2B">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc"/>
|
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
<incoming>sid-FD82C2A6-7C54-4890-901E-A7E864F7605C</incoming>
|
<incoming>sid-FD82C2A6-7C54-4890-901E-A7E864F7605C</incoming>
|
||||||
<outgoing>sid-47947925-21CD-46FF-8D3F-294B235AA4CF</outgoing>
|
<outgoing>sid-47947925-21CD-46FF-8D3F-294B235AA4CF</outgoing>
|
||||||
</task>
|
</task>
|
||||||
<parallelGateway gatewayDirection="Converging" id="sid-A1609BD5-1E4A-47AE-8648-1DD41D1B1D58" name="Join of Second Split">
|
<parallelGateway id="join_of_second_split" name="Join of Second Split" gatewayDirection="Converging">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff"/>
|
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
<incoming>sid-961AF51C-9935-410E-AAA4-105B19186F5E</incoming>
|
<incoming>sid-961AF51C-9935-410E-AAA4-105B19186F5E</incoming>
|
||||||
<incoming>sid-47947925-21CD-46FF-8D3F-294B235AA4CF</incoming>
|
<incoming>sid-47947925-21CD-46FF-8D3F-294B235AA4CF</incoming>
|
||||||
<outgoing>sid-AFA38469-CD5C-42A2-9473-2EAEBA61F0C0</outgoing>
|
<outgoing>sid-AFA38469-CD5C-42A2-9473-2EAEBA61F0C0</outgoing>
|
||||||
</parallelGateway>
|
</parallelGateway>
|
||||||
<task completionQuantity="1" id="sid-1946C635-7886-4687-844F-C644FA6222B8" isForCompensation="false" name="2 Done" startQuantity="1">
|
<task id="two_done" name="2 Done">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc"/>
|
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
<incoming>sid-AFA38469-CD5C-42A2-9473-2EAEBA61F0C0</incoming>
|
<incoming>sid-AFA38469-CD5C-42A2-9473-2EAEBA61F0C0</incoming>
|
||||||
<outgoing>sid-231F8A51-752F-4CB3-8FD1-23D153238344</outgoing>
|
<outgoing>sid-231F8A51-752F-4CB3-8FD1-23D153238344</outgoing>
|
||||||
</task>
|
</task>
|
||||||
<exclusiveGateway gatewayDirection="Diverging" id="sid-4C3B3C16-91DB-43E3-A816-FFEE572E61E1" name="">
|
<exclusiveGateway id="exclusive" name="" gatewayDirection="Diverging">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff"/>
|
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
<incoming>sid-2668AC98-39E4-4B12-9052-930528086CAC</incoming>
|
<incoming>sid-2668AC98-39E4-4B12-9052-930528086CAC</incoming>
|
||||||
<outgoing>sid-08D6385B-C6BB-45FC-A6BD-2369F392868D</outgoing>
|
<outgoing>sid-08D6385B-C6BB-45FC-A6BD-2369F392868D</outgoing>
|
||||||
<outgoing>sid-41205B5D-4DBA-4155-A0EE-7D71CE9AA459</outgoing>
|
<outgoing>sid-41205B5D-4DBA-4155-A0EE-7D71CE9AA459</outgoing>
|
||||||
</exclusiveGateway>
|
</exclusiveGateway>
|
||||||
<task completionQuantity="1" id="sid-55C018B8-C073-4292-9ED0-79BDE50E7498" isForCompensation="false" name="Done" startQuantity="1">
|
<task id="done" name="Done">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc"/>
|
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
<incoming>sid-08D6385B-C6BB-45FC-A6BD-2369F392868D</incoming>
|
<incoming>sid-08D6385B-C6BB-45FC-A6BD-2369F392868D</incoming>
|
||||||
<outgoing>sid-F6160C0E-216C-4D72-98D1-CC5549327D55</outgoing>
|
<outgoing>sid-F6160C0E-216C-4D72-98D1-CC5549327D55</outgoing>
|
||||||
</task>
|
</task>
|
||||||
<task completionQuantity="1" id="sid-E7904BFA-1F17-478E-91C9-C8A5B64190C9" isForCompensation="false" name="Go" startQuantity="1">
|
<task id="go" name="Go">
|
||||||
<extensionElements>
|
<extensionElements>
|
||||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc"/>
|
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc" />
|
||||||
</extensionElements>
|
</extensionElements>
|
||||||
<incoming>sid-41205B5D-4DBA-4155-A0EE-7D71CE9AA459</incoming>
|
<incoming>sid-41205B5D-4DBA-4155-A0EE-7D71CE9AA459</incoming>
|
||||||
<incoming>sid-F3994F51-FE54-4910-A1F4-E5895AA1A612</incoming>
|
<incoming>sid-F3994F51-FE54-4910-A1F4-E5895AA1A612</incoming>
|
||||||
<outgoing>sid-3E0EBE59-75C8-465C-90CC-197CE808A96E</outgoing>
|
<outgoing>sid-3E0EBE59-75C8-465C-90CC-197CE808A96E</outgoing>
|
||||||
</task>
|
</task>
|
||||||
<sequenceFlow id="sid-7E15C71B-DE9E-4788-B140-A647C99FDC94" name="" sourceRef="sid-349F8C0C-45EA-489C-84DD-1D944F48D778" targetRef="sid-57463471-693A-42A2-9EC6-6460BEDECA86"/>
|
<sequenceFlow id="sid-7E15C71B-DE9E-4788-B140-A647C99FDC94" name="" sourceRef="first_split" targetRef="one" />
|
||||||
<sequenceFlow id="sid-B6E22A74-A691-453A-A789-B9F8AF787D7C" name="" sourceRef="sid-349F8C0C-45EA-489C-84DD-1D944F48D778" targetRef="sid-CA089240-802A-4C32-9130-FB1A33DDCCC3"/>
|
<sequenceFlow id="sid-B6E22A74-A691-453A-A789-B9F8AF787D7C" name="" sourceRef="first_split" targetRef="two" />
|
||||||
<sequenceFlow id="sid-0895E09C-077C-4D12-8C11-31F28CBC7740" name="" sourceRef="sid-E976FBC2-266E-420F-8D4D-C8FBC6199090" targetRef="sid-F3A979E3-F586-4807-8223-1FAB5A5647B0"/>
|
<sequenceFlow id="sid-0895E09C-077C-4D12-8C11-31F28CBC7740" name="" sourceRef="join_of_first" targetRef="retry" />
|
||||||
<sequenceFlow id="sid-CAEAD081-6E73-4C98-8656-C67DA18F5140" name="" sourceRef="sid-CA089240-802A-4C32-9130-FB1A33DDCCC3" targetRef="sid-4F7F3AA6-4E8E-458D-BBEF-E03FC0646897"/>
|
<sequenceFlow id="sid-CAEAD081-6E73-4C98-8656-C67DA18F5140" name="" sourceRef="two" targetRef="second_split" />
|
||||||
<sequenceFlow id="sid-918C653D-0960-4223-9C28-78114F238BCC" name="" sourceRef="sid-4F7F3AA6-4E8E-458D-BBEF-E03FC0646897" targetRef="sid-ABD788A3-CD57-4280-A22A-260B3AEEE138"/>
|
<sequenceFlow id="sid-918C653D-0960-4223-9C28-78114F238BCC" name="" sourceRef="second_split" targetRef="two_a" />
|
||||||
<sequenceFlow id="sid-FD82C2A6-7C54-4890-901E-A7E864F7605C" name="" sourceRef="sid-4F7F3AA6-4E8E-458D-BBEF-E03FC0646897" targetRef="sid-E7B8898A-0D14-4E98-B3D7-736B94EE3FA7"/>
|
<sequenceFlow id="sid-FD82C2A6-7C54-4890-901E-A7E864F7605C" name="" sourceRef="second_split" targetRef="two_b" />
|
||||||
<sequenceFlow id="sid-961AF51C-9935-410E-AAA4-105B19186F5E" name="" sourceRef="sid-ABD788A3-CD57-4280-A22A-260B3AEEE138" targetRef="sid-A1609BD5-1E4A-47AE-8648-1DD41D1B1D58"/>
|
<sequenceFlow id="sid-961AF51C-9935-410E-AAA4-105B19186F5E" name="" sourceRef="two_a" targetRef="join_of_second_split" />
|
||||||
<sequenceFlow id="sid-47947925-21CD-46FF-8D3F-294B235AA4CF" name="" sourceRef="sid-E7B8898A-0D14-4E98-B3D7-736B94EE3FA7" targetRef="sid-A1609BD5-1E4A-47AE-8648-1DD41D1B1D58"/>
|
<sequenceFlow id="sid-47947925-21CD-46FF-8D3F-294B235AA4CF" name="" sourceRef="two_b" targetRef="join_of_second_split" />
|
||||||
<sequenceFlow id="sid-AFA38469-CD5C-42A2-9473-2EAEBA61F0C0" name="" sourceRef="sid-A1609BD5-1E4A-47AE-8648-1DD41D1B1D58" targetRef="sid-1946C635-7886-4687-844F-C644FA6222B8"/>
|
<sequenceFlow id="sid-AFA38469-CD5C-42A2-9473-2EAEBA61F0C0" name="" sourceRef="join_of_second_split" targetRef="two_done" />
|
||||||
<sequenceFlow id="sid-231F8A51-752F-4CB3-8FD1-23D153238344" name="" sourceRef="sid-1946C635-7886-4687-844F-C644FA6222B8" targetRef="sid-E976FBC2-266E-420F-8D4D-C8FBC6199090"/>
|
<sequenceFlow id="sid-231F8A51-752F-4CB3-8FD1-23D153238344" name="" sourceRef="two_done" targetRef="join_of_first" />
|
||||||
<sequenceFlow id="sid-607CB05E-8762-41B6-AD43-C3970661A99D" name="" sourceRef="sid-57463471-693A-42A2-9EC6-6460BEDECA86" targetRef="sid-E976FBC2-266E-420F-8D4D-C8FBC6199090"/>
|
<sequenceFlow id="join_of_first_split" name="" sourceRef="one" targetRef="join_of_first" />
|
||||||
<sequenceFlow id="sid-2668AC98-39E4-4B12-9052-930528086CAC" name="" sourceRef="sid-F3A979E3-F586-4807-8223-1FAB5A5647B0" targetRef="sid-4C3B3C16-91DB-43E3-A816-FFEE572E61E1"/>
|
<sequenceFlow id="sid-2668AC98-39E4-4B12-9052-930528086CAC" name="" sourceRef="retry" targetRef="exclusive" />
|
||||||
<sequenceFlow id="sid-08D6385B-C6BB-45FC-A6BD-2369F392868D" name="No" sourceRef="sid-4C3B3C16-91DB-43E3-A816-FFEE572E61E1" targetRef="sid-55C018B8-C073-4292-9ED0-79BDE50E7498"/>
|
<sequenceFlow id="sid-08D6385B-C6BB-45FC-A6BD-2369F392868D" name="No" sourceRef="exclusive" targetRef="done" />
|
||||||
<sequenceFlow id="sid-F6160C0E-216C-4D72-98D1-CC5549327D55" name="" sourceRef="sid-55C018B8-C073-4292-9ED0-79BDE50E7498" targetRef="sid-51816945-79BF-47F9-BA3C-E95ABAE3D1DB"/>
|
<sequenceFlow id="sid-F6160C0E-216C-4D72-98D1-CC5549327D55" name="" sourceRef="done" targetRef="end" />
|
||||||
<sequenceFlow id="sid-41205B5D-4DBA-4155-A0EE-7D71CE9AA459" name="Yes" sourceRef="sid-4C3B3C16-91DB-43E3-A816-FFEE572E61E1" targetRef="sid-E7904BFA-1F17-478E-91C9-C8A5B64190C9"/>
|
<sequenceFlow id="sid-41205B5D-4DBA-4155-A0EE-7D71CE9AA459" name="Yes" sourceRef="exclusive" targetRef="go" />
|
||||||
<sequenceFlow id="sid-3E0EBE59-75C8-465C-90CC-197CE808A96E" name="" sourceRef="sid-E7904BFA-1F17-478E-91C9-C8A5B64190C9" targetRef="sid-349F8C0C-45EA-489C-84DD-1D944F48D778"/>
|
<sequenceFlow id="sid-3E0EBE59-75C8-465C-90CC-197CE808A96E" name="" sourceRef="go" targetRef="first_split" />
|
||||||
<sequenceFlow id="sid-F3994F51-FE54-4910-A1F4-E5895AA1A612" name="" sourceRef="sid-B33EE043-AB93-4343-A1D4-7B267E2DAFBE" targetRef="sid-E7904BFA-1F17-478E-91C9-C8A5B64190C9"/>
|
<sequenceFlow id="sid-F3994F51-FE54-4910-A1F4-E5895AA1A612" name="" sourceRef="start" targetRef="go" />
|
||||||
</process>
|
</process>
|
||||||
<bpmndi:BPMNDiagram id="sid-162a2324-4820-489a-9df8-04591b6b429a">
|
<bpmndi:BPMNDiagram id="sid-162a2324-4820-489a-9df8-04591b6b429a">
|
||||||
<bpmndi:BPMNPlane bpmnElement="sid-5801c79c-823f-4040-b680-417ef5bcb3a2" id="sid-4c9e28b7-9050-4a64-8a52-634f8f2febc3">
|
<bpmndi:BPMNPlane id="sid-4c9e28b7-9050-4a64-8a52-634f8f2febc3" bpmnElement="sid-5801c79c-823f-4040-b680-417ef5bcb3a2">
|
||||||
<bpmndi:BPMNShape bpmnElement="sid-B2E5AD50-035A-4CE8-B8A3-B175A6767B00" id="sid-B2E5AD50-035A-4CE8-B8A3-B175A6767B00_gui" isHorizontal="true">
|
<bpmndi:BPMNShape id="sid-B2E5AD50-035A-4CE8-B8A3-B175A6767B00_gui" bpmnElement="sid-B2E5AD50-035A-4CE8-B8A3-B175A6767B00" isHorizontal="true">
|
||||||
<omgdc:Bounds height="619.0" width="794.0" x="120.0" y="90.0"/>
|
<omgdc:Bounds x="120" y="90" width="794" height="619" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape bpmnElement="sid-72009BCE-46B4-4B4B-AEAE-1E7199522142" id="sid-72009BCE-46B4-4B4B-AEAE-1E7199522142_gui" isHorizontal="true">
|
<bpmndi:BPMNShape id="sid-72009BCE-46B4-4B4B-AEAE-1E7199522142_gui" bpmnElement="sid-72009BCE-46B4-4B4B-AEAE-1E7199522142" isHorizontal="true">
|
||||||
<omgdc:Bounds height="619.0" width="764.0" x="150.0" y="90.0"/>
|
<omgdc:Bounds x="150" y="90" width="764" height="619" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape bpmnElement="sid-B33EE043-AB93-4343-A1D4-7B267E2DAFBE" id="sid-B33EE043-AB93-4343-A1D4-7B267E2DAFBE_gui">
|
<bpmndi:BPMNEdge id="sid-F3994F51-FE54-4910-A1F4-E5895AA1A612_gui" bpmnElement="sid-F3994F51-FE54-4910-A1F4-E5895AA1A612">
|
||||||
<omgdc:Bounds height="30.0" width="30.0" x="190.0" y="122.0"/>
|
<omgdi:waypoint x="220" y="137" />
|
||||||
</bpmndi:BPMNShape>
|
<omgdi:waypoint x="394" y="137" />
|
||||||
<bpmndi:BPMNShape bpmnElement="sid-349F8C0C-45EA-489C-84DD-1D944F48D778" id="sid-349F8C0C-45EA-489C-84DD-1D944F48D778_gui">
|
<omgdi:waypoint x="467" y="142" />
|
||||||
<omgdc:Bounds height="40.0" width="40.0" x="374.0" y="180.0"/>
|
</bpmndi:BPMNEdge>
|
||||||
</bpmndi:BPMNShape>
|
<bpmndi:BPMNEdge id="sid-3E0EBE59-75C8-465C-90CC-197CE808A96E_gui" bpmnElement="sid-3E0EBE59-75C8-465C-90CC-197CE808A96E">
|
||||||
<bpmndi:BPMNShape bpmnElement="sid-57463471-693A-42A2-9EC6-6460BEDECA86" id="sid-57463471-693A-42A2-9EC6-6460BEDECA86_gui">
|
<omgdi:waypoint x="467" y="167" />
|
||||||
<omgdc:Bounds height="80.0" width="100.0" x="510.0" y="214.0"/>
|
<omgdi:waypoint x="414" y="191" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNShape bpmnElement="sid-CA089240-802A-4C32-9130-FB1A33DDCCC3" id="sid-CA089240-802A-4C32-9130-FB1A33DDCCC3_gui">
|
<bpmndi:BPMNEdge id="sid-41205B5D-4DBA-4155-A0EE-7D71CE9AA459_gui" bpmnElement="sid-41205B5D-4DBA-4155-A0EE-7D71CE9AA459">
|
||||||
<omgdc:Bounds height="80.0" width="100.0" x="231.0" y="214.0"/>
|
<omgdi:waypoint x="794" y="405" />
|
||||||
</bpmndi:BPMNShape>
|
<omgdi:waypoint x="794.5" y="145" />
|
||||||
<bpmndi:BPMNShape bpmnElement="sid-E976FBC2-266E-420F-8D4D-C8FBC6199090" id="sid-E976FBC2-266E-420F-8D4D-C8FBC6199090_gui">
|
<omgdi:waypoint x="567" y="145" />
|
||||||
<omgdc:Bounds height="40.0" width="40.0" x="540.0" y="405.0"/>
|
</bpmndi:BPMNEdge>
|
||||||
</bpmndi:BPMNShape>
|
<bpmndi:BPMNEdge id="sid-F6160C0E-216C-4D72-98D1-CC5549327D55_gui" bpmnElement="sid-F6160C0E-216C-4D72-98D1-CC5549327D55">
|
||||||
<bpmndi:BPMNShape bpmnElement="sid-F3A979E3-F586-4807-8223-1FAB5A5647B0" id="sid-F3A979E3-F586-4807-8223-1FAB5A5647B0_gui">
|
<omgdi:waypoint x="794" y="577" />
|
||||||
<omgdc:Bounds height="80.0" width="100.0" x="615.0" y="385.0"/>
|
<omgdi:waypoint x="794" y="630" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNShape bpmnElement="sid-51816945-79BF-47F9-BA3C-E95ABAE3D1DB" id="sid-51816945-79BF-47F9-BA3C-E95ABAE3D1DB_gui">
|
<bpmndi:BPMNEdge id="sid-08D6385B-C6BB-45FC-A6BD-2369F392868D_gui" bpmnElement="sid-08D6385B-C6BB-45FC-A6BD-2369F392868D">
|
||||||
<omgdc:Bounds height="28.0" width="28.0" x="780.0" y="630.0"/>
|
<omgdi:waypoint x="794" y="445" />
|
||||||
</bpmndi:BPMNShape>
|
<omgdi:waypoint x="794" y="497" />
|
||||||
<bpmndi:BPMNShape bpmnElement="sid-4F7F3AA6-4E8E-458D-BBEF-E03FC0646897" id="sid-4F7F3AA6-4E8E-458D-BBEF-E03FC0646897_gui">
|
</bpmndi:BPMNEdge>
|
||||||
<omgdc:Bounds height="40.0" width="40.0" x="261.0" y="338.0"/>
|
<bpmndi:BPMNEdge id="sid-2668AC98-39E4-4B12-9052-930528086CAC_gui" bpmnElement="sid-2668AC98-39E4-4B12-9052-930528086CAC">
|
||||||
</bpmndi:BPMNShape>
|
<omgdi:waypoint x="715" y="425" />
|
||||||
<bpmndi:BPMNShape bpmnElement="sid-ABD788A3-CD57-4280-A22A-260B3AEEE138" id="sid-ABD788A3-CD57-4280-A22A-260B3AEEE138_gui">
|
<omgdi:waypoint x="774" y="425" />
|
||||||
<omgdc:Bounds height="80.0" width="100.0" x="177.0" y="417.0"/>
|
</bpmndi:BPMNEdge>
|
||||||
</bpmndi:BPMNShape>
|
<bpmndi:BPMNEdge id="sid-607CB05E-8762-41B6-AD43-C3970661A99D_gui" bpmnElement="join_of_first_split">
|
||||||
<bpmndi:BPMNShape bpmnElement="sid-E7B8898A-0D14-4E98-B3D7-736B94EE3FA7" id="sid-E7B8898A-0D14-4E98-B3D7-736B94EE3FA7_gui">
|
<omgdi:waypoint x="560" y="294" />
|
||||||
<omgdc:Bounds height="80.0" width="100.0" x="344.0" y="408.0"/>
|
<omgdi:waypoint x="560" y="405" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNShape bpmnElement="sid-A1609BD5-1E4A-47AE-8648-1DD41D1B1D58" id="sid-A1609BD5-1E4A-47AE-8648-1DD41D1B1D58_gui">
|
<bpmndi:BPMNEdge id="sid-231F8A51-752F-4CB3-8FD1-23D153238344_gui" bpmnElement="sid-231F8A51-752F-4CB3-8FD1-23D153238344">
|
||||||
<omgdc:Bounds height="40.0" width="40.0" x="285.0" y="555.0"/>
|
<omgdi:waypoint x="475" y="535" />
|
||||||
</bpmndi:BPMNShape>
|
<omgdi:waypoint x="491" y="425" />
|
||||||
<bpmndi:BPMNShape bpmnElement="sid-1946C635-7886-4687-844F-C644FA6222B8" id="sid-1946C635-7886-4687-844F-C644FA6222B8_gui">
|
<omgdi:waypoint x="540" y="425" />
|
||||||
<omgdc:Bounds height="80.0" width="100.0" x="420.0" y="535.0"/>
|
</bpmndi:BPMNEdge>
|
||||||
</bpmndi:BPMNShape>
|
<bpmndi:BPMNEdge id="sid-AFA38469-CD5C-42A2-9473-2EAEBA61F0C0_gui" bpmnElement="sid-AFA38469-CD5C-42A2-9473-2EAEBA61F0C0">
|
||||||
<bpmndi:BPMNShape bpmnElement="sid-4C3B3C16-91DB-43E3-A816-FFEE572E61E1" id="sid-4C3B3C16-91DB-43E3-A816-FFEE572E61E1_gui" isMarkerVisible="true">
|
<omgdi:waypoint x="325" y="575" />
|
||||||
<omgdc:Bounds height="40.0" width="40.0" x="774.0" y="405.0"/>
|
<omgdi:waypoint x="420" y="575" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNShape bpmnElement="sid-55C018B8-C073-4292-9ED0-79BDE50E7498" id="sid-55C018B8-C073-4292-9ED0-79BDE50E7498_gui">
|
<bpmndi:BPMNEdge id="sid-47947925-21CD-46FF-8D3F-294B235AA4CF_gui" bpmnElement="sid-47947925-21CD-46FF-8D3F-294B235AA4CF">
|
||||||
<omgdc:Bounds height="80.0" width="100.0" x="744.0" y="497.0"/>
|
<omgdi:waypoint x="344" y="448" />
|
||||||
</bpmndi:BPMNShape>
|
<omgdi:waypoint x="305" y="448" />
|
||||||
<bpmndi:BPMNShape bpmnElement="sid-E7904BFA-1F17-478E-91C9-C8A5B64190C9" id="sid-E7904BFA-1F17-478E-91C9-C8A5B64190C9_gui">
|
<omgdi:waypoint x="305" y="555" />
|
||||||
<omgdc:Bounds height="80.0" width="100.0" x="467.0" y="105.0"/>
|
</bpmndi:BPMNEdge>
|
||||||
</bpmndi:BPMNShape>
|
<bpmndi:BPMNEdge id="sid-961AF51C-9935-410E-AAA4-105B19186F5E_gui" bpmnElement="sid-961AF51C-9935-410E-AAA4-105B19186F5E">
|
||||||
<bpmndi:BPMNEdge bpmnElement="sid-7E15C71B-DE9E-4788-B140-A647C99FDC94" id="sid-7E15C71B-DE9E-4788-B140-A647C99FDC94_gui">
|
<omgdi:waypoint x="277" y="457" />
|
||||||
<omgdi:waypoint x="414.0" y="206.0"/>
|
<omgdi:waypoint x="305" y="457" />
|
||||||
<omgdi:waypoint x="510.0" y="238.0"/>
|
<omgdi:waypoint x="305" y="555" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge bpmnElement="sid-08D6385B-C6BB-45FC-A6BD-2369F392868D" id="sid-08D6385B-C6BB-45FC-A6BD-2369F392868D_gui">
|
<bpmndi:BPMNEdge id="sid-FD82C2A6-7C54-4890-901E-A7E864F7605C_gui" bpmnElement="sid-FD82C2A6-7C54-4890-901E-A7E864F7605C">
|
||||||
<omgdi:waypoint x="794.0" y="445.0"/>
|
<omgdi:waypoint x="301" y="358" />
|
||||||
<omgdi:waypoint x="794.0" y="497.0"/>
|
<omgdi:waypoint x="394" y="358.5" />
|
||||||
</bpmndi:BPMNEdge>
|
<omgdi:waypoint x="394" y="408" />
|
||||||
<bpmndi:BPMNEdge bpmnElement="sid-F3994F51-FE54-4910-A1F4-E5895AA1A612" id="sid-F3994F51-FE54-4910-A1F4-E5895AA1A612_gui">
|
</bpmndi:BPMNEdge>
|
||||||
<omgdi:waypoint x="220.0" y="137.0"/>
|
<bpmndi:BPMNEdge id="sid-918C653D-0960-4223-9C28-78114F238BCC_gui" bpmnElement="sid-918C653D-0960-4223-9C28-78114F238BCC">
|
||||||
<omgdi:waypoint x="394.0" y="137.0"/>
|
<omgdi:waypoint x="261" y="358" />
|
||||||
<omgdi:waypoint x="467.0" y="142.0"/>
|
<omgdi:waypoint x="227" y="358.5" />
|
||||||
</bpmndi:BPMNEdge>
|
<omgdi:waypoint x="227" y="417" />
|
||||||
<bpmndi:BPMNEdge bpmnElement="sid-231F8A51-752F-4CB3-8FD1-23D153238344" id="sid-231F8A51-752F-4CB3-8FD1-23D153238344_gui">
|
</bpmndi:BPMNEdge>
|
||||||
<omgdi:waypoint x="475.0" y="535.0"/>
|
<bpmndi:BPMNEdge id="sid-CAEAD081-6E73-4C98-8656-C67DA18F5140_gui" bpmnElement="sid-CAEAD081-6E73-4C98-8656-C67DA18F5140">
|
||||||
<omgdi:waypoint x="491.0" y="425.0"/>
|
<omgdi:waypoint x="281" y="294" />
|
||||||
<omgdi:waypoint x="540.0" y="425.0"/>
|
<omgdi:waypoint x="281" y="338" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge bpmnElement="sid-F6160C0E-216C-4D72-98D1-CC5549327D55" id="sid-F6160C0E-216C-4D72-98D1-CC5549327D55_gui">
|
<bpmndi:BPMNEdge id="sid-0895E09C-077C-4D12-8C11-31F28CBC7740_gui" bpmnElement="sid-0895E09C-077C-4D12-8C11-31F28CBC7740">
|
||||||
<omgdi:waypoint x="794.0" y="577.0"/>
|
<omgdi:waypoint x="580" y="425" />
|
||||||
<omgdi:waypoint x="794.0" y="630.0"/>
|
<omgdi:waypoint x="615" y="425" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge bpmnElement="sid-B6E22A74-A691-453A-A789-B9F8AF787D7C" id="sid-B6E22A74-A691-453A-A789-B9F8AF787D7C_gui">
|
<bpmndi:BPMNEdge id="sid-B6E22A74-A691-453A-A789-B9F8AF787D7C_gui" bpmnElement="sid-B6E22A74-A691-453A-A789-B9F8AF787D7C">
|
||||||
<omgdi:waypoint x="394.0" y="220.0"/>
|
<omgdi:waypoint x="394" y="220" />
|
||||||
<omgdi:waypoint x="394.5" y="254.0"/>
|
<omgdi:waypoint x="394.5" y="254" />
|
||||||
<omgdi:waypoint x="331.0" y="254.0"/>
|
<omgdi:waypoint x="331" y="254" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge bpmnElement="sid-2668AC98-39E4-4B12-9052-930528086CAC" id="sid-2668AC98-39E4-4B12-9052-930528086CAC_gui">
|
<bpmndi:BPMNEdge id="sid-7E15C71B-DE9E-4788-B140-A647C99FDC94_gui" bpmnElement="sid-7E15C71B-DE9E-4788-B140-A647C99FDC94">
|
||||||
<omgdi:waypoint x="715.0" y="425.0"/>
|
<omgdi:waypoint x="414" y="206" />
|
||||||
<omgdi:waypoint x="774.0" y="425.0"/>
|
<omgdi:waypoint x="510" y="238" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge bpmnElement="sid-CAEAD081-6E73-4C98-8656-C67DA18F5140" id="sid-CAEAD081-6E73-4C98-8656-C67DA18F5140_gui">
|
<bpmndi:BPMNShape id="sid-B33EE043-AB93-4343-A1D4-7B267E2DAFBE_gui" bpmnElement="start">
|
||||||
<omgdi:waypoint x="281.0" y="294.0"/>
|
<omgdc:Bounds x="190" y="122" width="30" height="30" />
|
||||||
<omgdi:waypoint x="281.0" y="338.0"/>
|
</bpmndi:BPMNShape>
|
||||||
</bpmndi:BPMNEdge>
|
<bpmndi:BPMNShape id="sid-349F8C0C-45EA-489C-84DD-1D944F48D778_gui" bpmnElement="first_split">
|
||||||
<bpmndi:BPMNEdge bpmnElement="sid-918C653D-0960-4223-9C28-78114F238BCC" id="sid-918C653D-0960-4223-9C28-78114F238BCC_gui">
|
<omgdc:Bounds x="374" y="180" width="40" height="40" />
|
||||||
<omgdi:waypoint x="261.0" y="358.0"/>
|
<bpmndi:BPMNLabel>
|
||||||
<omgdi:waypoint x="227.0" y="358.5"/>
|
<omgdc:Bounds x="371" y="220" width="46" height="14" />
|
||||||
<omgdi:waypoint x="227.0" y="417.0"/>
|
</bpmndi:BPMNLabel>
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNEdge bpmnElement="sid-FD82C2A6-7C54-4890-901E-A7E864F7605C" id="sid-FD82C2A6-7C54-4890-901E-A7E864F7605C_gui">
|
<bpmndi:BPMNShape id="sid-57463471-693A-42A2-9EC6-6460BEDECA86_gui" bpmnElement="one">
|
||||||
<omgdi:waypoint x="301.0" y="358.0"/>
|
<omgdc:Bounds x="510" y="214" width="100" height="80" />
|
||||||
<omgdi:waypoint x="394.0" y="358.5"/>
|
</bpmndi:BPMNShape>
|
||||||
<omgdi:waypoint x="394.0" y="408.0"/>
|
<bpmndi:BPMNShape id="sid-CA089240-802A-4C32-9130-FB1A33DDCCC3_gui" bpmnElement="two">
|
||||||
</bpmndi:BPMNEdge>
|
<omgdc:Bounds x="231" y="214" width="100" height="80" />
|
||||||
<bpmndi:BPMNEdge bpmnElement="sid-41205B5D-4DBA-4155-A0EE-7D71CE9AA459" id="sid-41205B5D-4DBA-4155-A0EE-7D71CE9AA459_gui">
|
</bpmndi:BPMNShape>
|
||||||
<omgdi:waypoint x="794.0" y="405.0"/>
|
<bpmndi:BPMNShape id="sid-E976FBC2-266E-420F-8D4D-C8FBC6199090_gui" bpmnElement="join_of_first">
|
||||||
<omgdi:waypoint x="794.5" y="145.0"/>
|
<omgdc:Bounds x="540" y="405" width="40" height="40" />
|
||||||
<omgdi:waypoint x="567.0" y="145.0"/>
|
<bpmndi:BPMNLabel>
|
||||||
</bpmndi:BPMNEdge>
|
<omgdc:Bounds x="519" y="445" width="82" height="14" />
|
||||||
<bpmndi:BPMNEdge bpmnElement="sid-607CB05E-8762-41B6-AD43-C3970661A99D" id="sid-607CB05E-8762-41B6-AD43-C3970661A99D_gui">
|
</bpmndi:BPMNLabel>
|
||||||
<omgdi:waypoint x="560.0" y="294.0"/>
|
</bpmndi:BPMNShape>
|
||||||
<omgdi:waypoint x="560.0" y="405.0"/>
|
<bpmndi:BPMNShape id="sid-F3A979E3-F586-4807-8223-1FAB5A5647B0_gui" bpmnElement="retry">
|
||||||
</bpmndi:BPMNEdge>
|
<omgdc:Bounds x="615" y="385" width="100" height="80" />
|
||||||
<bpmndi:BPMNEdge bpmnElement="sid-AFA38469-CD5C-42A2-9473-2EAEBA61F0C0" id="sid-AFA38469-CD5C-42A2-9473-2EAEBA61F0C0_gui">
|
</bpmndi:BPMNShape>
|
||||||
<omgdi:waypoint x="325.0" y="575.0"/>
|
<bpmndi:BPMNShape id="sid-51816945-79BF-47F9-BA3C-E95ABAE3D1DB_gui" bpmnElement="end">
|
||||||
<omgdi:waypoint x="420.0" y="575.0"/>
|
<omgdc:Bounds x="780" y="630" width="28" height="28" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNEdge bpmnElement="sid-961AF51C-9935-410E-AAA4-105B19186F5E" id="sid-961AF51C-9935-410E-AAA4-105B19186F5E_gui">
|
<bpmndi:BPMNShape id="sid-4F7F3AA6-4E8E-458D-BBEF-E03FC0646897_gui" bpmnElement="second_split">
|
||||||
<omgdi:waypoint x="277.0" y="457.0"/>
|
<omgdc:Bounds x="261" y="338" width="40" height="40" />
|
||||||
<omgdi:waypoint x="305.0" y="457.0"/>
|
<bpmndi:BPMNLabel>
|
||||||
<omgdi:waypoint x="305.0" y="555.0"/>
|
<omgdc:Bounds x="250" y="378" width="62" height="14" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNLabel>
|
||||||
<bpmndi:BPMNEdge bpmnElement="sid-3E0EBE59-75C8-465C-90CC-197CE808A96E" id="sid-3E0EBE59-75C8-465C-90CC-197CE808A96E_gui">
|
</bpmndi:BPMNShape>
|
||||||
<omgdi:waypoint x="467.0" y="167.0"/>
|
<bpmndi:BPMNShape id="sid-ABD788A3-CD57-4280-A22A-260B3AEEE138_gui" bpmnElement="two_a">
|
||||||
<omgdi:waypoint x="414.0" y="191.0"/>
|
<omgdc:Bounds x="177" y="417" width="100" height="80" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNEdge bpmnElement="sid-0895E09C-077C-4D12-8C11-31F28CBC7740" id="sid-0895E09C-077C-4D12-8C11-31F28CBC7740_gui">
|
<bpmndi:BPMNShape id="sid-E7B8898A-0D14-4E98-B3D7-736B94EE3FA7_gui" bpmnElement="two_b">
|
||||||
<omgdi:waypoint x="580.0" y="425.0"/>
|
<omgdc:Bounds x="344" y="408" width="100" height="80" />
|
||||||
<omgdi:waypoint x="615.0" y="425.0"/>
|
</bpmndi:BPMNShape>
|
||||||
</bpmndi:BPMNEdge>
|
<bpmndi:BPMNShape id="sid-A1609BD5-1E4A-47AE-8648-1DD41D1B1D58_gui" bpmnElement="join_of_second_split">
|
||||||
<bpmndi:BPMNEdge bpmnElement="sid-47947925-21CD-46FF-8D3F-294B235AA4CF" id="sid-47947925-21CD-46FF-8D3F-294B235AA4CF_gui">
|
<omgdc:Bounds x="285" y="555" width="40" height="40" />
|
||||||
<omgdi:waypoint x="344.0" y="448.0"/>
|
<bpmndi:BPMNLabel>
|
||||||
<omgdi:waypoint x="305.0" y="448.0"/>
|
<omgdc:Bounds x="269" y="595" width="73" height="27" />
|
||||||
<omgdi:waypoint x="305.0" y="555.0"/>
|
</bpmndi:BPMNLabel>
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNShape>
|
||||||
</bpmndi:BPMNPlane>
|
<bpmndi:BPMNShape id="sid-1946C635-7886-4687-844F-C644FA6222B8_gui" bpmnElement="two_done">
|
||||||
</bpmndi:BPMNDiagram>
|
<omgdc:Bounds x="420" y="535" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="sid-4C3B3C16-91DB-43E3-A816-FFEE572E61E1_gui" bpmnElement="exclusive" isMarkerVisible="true">
|
||||||
|
<omgdc:Bounds x="774" y="405" width="40" height="40" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="sid-55C018B8-C073-4292-9ED0-79BDE50E7498_gui" bpmnElement="done">
|
||||||
|
<omgdc:Bounds x="744" y="497" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="sid-E7904BFA-1F17-478E-91C9-C8A5B64190C9_gui" bpmnElement="go">
|
||||||
|
<omgdc:Bounds x="467" y="105" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
</bpmndi:BPMNPlane>
|
||||||
|
</bpmndi:BPMNDiagram>
|
||||||
</definitions>
|
</definitions>
|
||||||
|
@ -1,104 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1svhxil" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.11.1" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.15.0">
|
|
||||||
<bpmn:process id="main" isExecutable="true">
|
|
||||||
<bpmn:startEvent id="StartEvent_1">
|
|
||||||
<bpmn:outgoing>Flow_0j648np</bpmn:outgoing>
|
|
||||||
</bpmn:startEvent>
|
|
||||||
<bpmn:exclusiveGateway id="Gateway_1hq5zma" default="Flow_13cp5nc">
|
|
||||||
<bpmn:incoming>Flow_0j648np</bpmn:incoming>
|
|
||||||
<bpmn:incoming>modify</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_13cp5nc</bpmn:outgoing>
|
|
||||||
</bpmn:exclusiveGateway>
|
|
||||||
<bpmn:sequenceFlow id="Flow_0j648np" sourceRef="StartEvent_1" targetRef="Gateway_1hq5zma" />
|
|
||||||
<bpmn:task id="task_1" name="Task 1">
|
|
||||||
<bpmn:incoming>Flow_13cp5nc</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_1r81vou</bpmn:outgoing>
|
|
||||||
</bpmn:task>
|
|
||||||
<bpmn:sequenceFlow id="Flow_13cp5nc" sourceRef="Gateway_1hq5zma" targetRef="task_1" />
|
|
||||||
<bpmn:task id="task_2" name="Task 2">
|
|
||||||
<bpmn:incoming>Flow_0m5s7t9</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_0p7c88x</bpmn:outgoing>
|
|
||||||
</bpmn:task>
|
|
||||||
<bpmn:endEvent id="Event_07pdq0w">
|
|
||||||
<bpmn:incoming>Flow_1gm7381</bpmn:incoming>
|
|
||||||
<bpmn:incoming>Flow_0p7c88x</bpmn:incoming>
|
|
||||||
</bpmn:endEvent>
|
|
||||||
<bpmn:boundaryEvent id="timer" attachedToRef="task_1">
|
|
||||||
<bpmn:outgoing>Flow_0m5s7t9</bpmn:outgoing>
|
|
||||||
<bpmn:timerEventDefinition id="TimerEventDefinition_0hu2ovu">
|
|
||||||
<bpmn:timeDuration xsi:type="bpmn:tFormalExpression">"PT60S"</bpmn:timeDuration>
|
|
||||||
</bpmn:timerEventDefinition>
|
|
||||||
</bpmn:boundaryEvent>
|
|
||||||
<bpmn:sequenceFlow id="Flow_0m5s7t9" sourceRef="timer" targetRef="task_2" />
|
|
||||||
<bpmn:exclusiveGateway id="Gateway_123uzx5" default="Flow_1gm7381">
|
|
||||||
<bpmn:incoming>Flow_1r81vou</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>modify</bpmn:outgoing>
|
|
||||||
<bpmn:outgoing>Flow_1gm7381</bpmn:outgoing>
|
|
||||||
</bpmn:exclusiveGateway>
|
|
||||||
<bpmn:sequenceFlow id="Flow_1r81vou" sourceRef="task_1" targetRef="Gateway_123uzx5" />
|
|
||||||
<bpmn:sequenceFlow id="modify" name="Modify " sourceRef="Gateway_123uzx5" targetRef="Gateway_1hq5zma">
|
|
||||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">modify</bpmn:conditionExpression>
|
|
||||||
</bpmn:sequenceFlow>
|
|
||||||
<bpmn:sequenceFlow id="Flow_1gm7381" sourceRef="Gateway_123uzx5" targetRef="Event_07pdq0w" />
|
|
||||||
<bpmn:sequenceFlow id="Flow_0p7c88x" sourceRef="task_2" targetRef="Event_07pdq0w" />
|
|
||||||
</bpmn:process>
|
|
||||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
|
||||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="main">
|
|
||||||
<bpmndi:BPMNEdge id="Flow_0j648np_di" bpmnElement="Flow_0j648np">
|
|
||||||
<di:waypoint x="215" y="197" />
|
|
||||||
<di:waypoint x="265" y="197" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_13cp5nc_di" bpmnElement="Flow_13cp5nc">
|
|
||||||
<di:waypoint x="315" y="197" />
|
|
||||||
<di:waypoint x="370" y="197" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_0m5s7t9_di" bpmnElement="Flow_0m5s7t9">
|
|
||||||
<di:waypoint x="420" y="255" />
|
|
||||||
<di:waypoint x="420" y="300" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1r81vou_di" bpmnElement="Flow_1r81vou">
|
|
||||||
<di:waypoint x="470" y="197" />
|
|
||||||
<di:waypoint x="525" y="197" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1l30w6o_di" bpmnElement="modify">
|
|
||||||
<di:waypoint x="550" y="172" />
|
|
||||||
<di:waypoint x="550" y="100" />
|
|
||||||
<di:waypoint x="290" y="100" />
|
|
||||||
<di:waypoint x="290" y="172" />
|
|
||||||
<bpmndi:BPMNLabel>
|
|
||||||
<dc:Bounds x="404" y="82" width="33" height="27" />
|
|
||||||
</bpmndi:BPMNLabel>
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1gm7381_di" bpmnElement="Flow_1gm7381">
|
|
||||||
<di:waypoint x="575" y="197" />
|
|
||||||
<di:waypoint x="632" y="197" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_0p7c88x_di" bpmnElement="Flow_0p7c88x">
|
|
||||||
<di:waypoint x="470" y="340" />
|
|
||||||
<di:waypoint x="650" y="340" />
|
|
||||||
<di:waypoint x="650" y="215" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
|
||||||
<dc:Bounds x="179" y="179" width="36" height="36" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Gateway_1hq5zma_di" bpmnElement="Gateway_1hq5zma" isMarkerVisible="true">
|
|
||||||
<dc:Bounds x="265" y="172" width="50" height="50" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Activity_1f3jg2c_di" bpmnElement="task_1">
|
|
||||||
<dc:Bounds x="370" y="157" width="100" height="80" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Activity_1r0ra56_di" bpmnElement="task_2">
|
|
||||||
<dc:Bounds x="370" y="300" width="100" height="80" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Gateway_123uzx5_di" bpmnElement="Gateway_123uzx5" isMarkerVisible="true">
|
|
||||||
<dc:Bounds x="525" y="172" width="50" height="50" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Event_07pdq0w_di" bpmnElement="Event_07pdq0w">
|
|
||||||
<dc:Bounds x="632" y="179" width="36" height="36" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Event_1g1bbcs_di" bpmnElement="timer">
|
|
||||||
<dc:Bounds x="402" y="219" width="36" height="36" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
</bpmndi:BPMNPlane>
|
|
||||||
</bpmndi:BPMNDiagram>
|
|
||||||
</bpmn:definitions>
|
|
@ -0,0 +1,600 @@
|
|||||||
|
{
|
||||||
|
"serializer_version": "1.1",
|
||||||
|
"data": {},
|
||||||
|
"last_task": "65ef57f0-3fbf-4851-b7c8-a03de9a9062d",
|
||||||
|
"success": true,
|
||||||
|
"tasks": {
|
||||||
|
"fcccd5d5-8e9c-4dba-91c3-5b2ed44bb332": {
|
||||||
|
"id": "fcccd5d5-8e9c-4dba-91c3-5b2ed44bb332",
|
||||||
|
"parent": null,
|
||||||
|
"children": [
|
||||||
|
"e9523ea2-0474-4c36-a7c2-24ff56633ed7"
|
||||||
|
],
|
||||||
|
"last_state_change": 1678818080.7799659,
|
||||||
|
"state": 32,
|
||||||
|
"task_spec": "Root",
|
||||||
|
"triggered": false,
|
||||||
|
"workflow_name": "loops",
|
||||||
|
"internal_data": {},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
"e9523ea2-0474-4c36-a7c2-24ff56633ed7": {
|
||||||
|
"id": "e9523ea2-0474-4c36-a7c2-24ff56633ed7",
|
||||||
|
"parent": "fcccd5d5-8e9c-4dba-91c3-5b2ed44bb332",
|
||||||
|
"children": [
|
||||||
|
"af4a1c32-12a7-46a7-b985-284fef1cb993"
|
||||||
|
],
|
||||||
|
"last_state_change": 1678818080.7888825,
|
||||||
|
"state": 32,
|
||||||
|
"task_spec": "Start",
|
||||||
|
"triggered": false,
|
||||||
|
"workflow_name": "loops",
|
||||||
|
"internal_data": {},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
"af4a1c32-12a7-46a7-b985-284fef1cb993": {
|
||||||
|
"id": "af4a1c32-12a7-46a7-b985-284fef1cb993",
|
||||||
|
"parent": "e9523ea2-0474-4c36-a7c2-24ff56633ed7",
|
||||||
|
"children": [
|
||||||
|
"65ef57f0-3fbf-4851-b7c8-a03de9a9062d"
|
||||||
|
],
|
||||||
|
"last_state_change": 1678818080.7926495,
|
||||||
|
"state": 32,
|
||||||
|
"task_spec": "StartEvent_1",
|
||||||
|
"triggered": false,
|
||||||
|
"workflow_name": "loops",
|
||||||
|
"internal_data": {
|
||||||
|
"event_fired": true
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
"65ef57f0-3fbf-4851-b7c8-a03de9a9062d": {
|
||||||
|
"id": "65ef57f0-3fbf-4851-b7c8-a03de9a9062d",
|
||||||
|
"parent": "af4a1c32-12a7-46a7-b985-284fef1cb993",
|
||||||
|
"children": [
|
||||||
|
"b5200074-8196-40d3-8f83-204cf132856c"
|
||||||
|
],
|
||||||
|
"last_state_change": 1678818080.7970355,
|
||||||
|
"state": 32,
|
||||||
|
"task_spec": "initialize",
|
||||||
|
"triggered": false,
|
||||||
|
"workflow_name": "loops",
|
||||||
|
"internal_data": {},
|
||||||
|
"data": {
|
||||||
|
"counter": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"b5200074-8196-40d3-8f83-204cf132856c": {
|
||||||
|
"id": "b5200074-8196-40d3-8f83-204cf132856c",
|
||||||
|
"parent": "65ef57f0-3fbf-4851-b7c8-a03de9a9062d",
|
||||||
|
"children": [
|
||||||
|
"2ab8ec7f-a9ee-4891-a7a9-20250e2ab816"
|
||||||
|
],
|
||||||
|
"last_state_change": 1678818091.420421,
|
||||||
|
"state": 16,
|
||||||
|
"task_spec": "TIMER_EVENT",
|
||||||
|
"triggered": false,
|
||||||
|
"workflow_name": "loops",
|
||||||
|
"internal_data": {
|
||||||
|
"event_value": "2023-03-14T18:21:20.809141+00:00",
|
||||||
|
"event_fired": true
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"counter": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"2ab8ec7f-a9ee-4891-a7a9-20250e2ab816": {
|
||||||
|
"id": "2ab8ec7f-a9ee-4891-a7a9-20250e2ab816",
|
||||||
|
"parent": "b5200074-8196-40d3-8f83-204cf132856c",
|
||||||
|
"children": [
|
||||||
|
"259de884-e162-4e0b-8c86-d4827870e2ca"
|
||||||
|
],
|
||||||
|
"last_state_change": 1678818080.7815213,
|
||||||
|
"state": 4,
|
||||||
|
"task_spec": "increment_counter",
|
||||||
|
"triggered": false,
|
||||||
|
"workflow_name": "loops",
|
||||||
|
"internal_data": {},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
"259de884-e162-4e0b-8c86-d4827870e2ca": {
|
||||||
|
"id": "259de884-e162-4e0b-8c86-d4827870e2ca",
|
||||||
|
"parent": "2ab8ec7f-a9ee-4891-a7a9-20250e2ab816",
|
||||||
|
"children": [
|
||||||
|
"bd7c4868-142b-487f-9ed3-6a384762dd55"
|
||||||
|
],
|
||||||
|
"last_state_change": 1678818080.7818034,
|
||||||
|
"state": 4,
|
||||||
|
"task_spec": "Activity_0w5u4k4",
|
||||||
|
"triggered": false,
|
||||||
|
"workflow_name": "loops",
|
||||||
|
"internal_data": {},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
"bd7c4868-142b-487f-9ed3-6a384762dd55": {
|
||||||
|
"id": "bd7c4868-142b-487f-9ed3-6a384762dd55",
|
||||||
|
"parent": "259de884-e162-4e0b-8c86-d4827870e2ca",
|
||||||
|
"children": [
|
||||||
|
"2fc6691e-f99b-46f2-bebc-7ba729880d13",
|
||||||
|
"3a14ea66-76b3-4442-8cb3-71a89b9b92fd"
|
||||||
|
],
|
||||||
|
"last_state_change": 1678818080.782314,
|
||||||
|
"state": 4,
|
||||||
|
"task_spec": "Gateway_over_20",
|
||||||
|
"triggered": false,
|
||||||
|
"workflow_name": "loops",
|
||||||
|
"internal_data": {},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
"2fc6691e-f99b-46f2-bebc-7ba729880d13": {
|
||||||
|
"id": "2fc6691e-f99b-46f2-bebc-7ba729880d13",
|
||||||
|
"parent": "bd7c4868-142b-487f-9ed3-6a384762dd55",
|
||||||
|
"children": [
|
||||||
|
"655b5f82-14dd-4edd-b88d-94e8cce02e78"
|
||||||
|
],
|
||||||
|
"last_state_change": 1678818080.783402,
|
||||||
|
"state": 1,
|
||||||
|
"task_spec": "end_event5",
|
||||||
|
"triggered": false,
|
||||||
|
"workflow_name": "loops",
|
||||||
|
"internal_data": {},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
"655b5f82-14dd-4edd-b88d-94e8cce02e78": {
|
||||||
|
"id": "655b5f82-14dd-4edd-b88d-94e8cce02e78",
|
||||||
|
"parent": "2fc6691e-f99b-46f2-bebc-7ba729880d13",
|
||||||
|
"children": [],
|
||||||
|
"last_state_change": 1678818080.7840528,
|
||||||
|
"state": 1,
|
||||||
|
"task_spec": "loops.EndJoin",
|
||||||
|
"triggered": false,
|
||||||
|
"workflow_name": "loops",
|
||||||
|
"internal_data": {},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
"3a14ea66-76b3-4442-8cb3-71a89b9b92fd": {
|
||||||
|
"id": "3a14ea66-76b3-4442-8cb3-71a89b9b92fd",
|
||||||
|
"parent": "bd7c4868-142b-487f-9ed3-6a384762dd55",
|
||||||
|
"children": [],
|
||||||
|
"last_state_change": 1678818080.7835343,
|
||||||
|
"state": 1,
|
||||||
|
"task_spec": "return_to_TIMER_EVENT",
|
||||||
|
"triggered": false,
|
||||||
|
"workflow_name": "loops",
|
||||||
|
"internal_data": {},
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "fcccd5d5-8e9c-4dba-91c3-5b2ed44bb332",
|
||||||
|
"spec": {
|
||||||
|
"name": "loops",
|
||||||
|
"description": "loops",
|
||||||
|
"file": "/home/essweine/work/sartography/code/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/too_many_loops.bpmn",
|
||||||
|
"task_specs": {
|
||||||
|
"Start": {
|
||||||
|
"id": "loops_1",
|
||||||
|
"name": "Start",
|
||||||
|
"description": "",
|
||||||
|
"manual": false,
|
||||||
|
"internal": false,
|
||||||
|
"lookahead": 2,
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
"StartEvent_1"
|
||||||
|
],
|
||||||
|
"typename": "StartTask"
|
||||||
|
},
|
||||||
|
"loops.EndJoin": {
|
||||||
|
"id": "loops_2",
|
||||||
|
"name": "loops.EndJoin",
|
||||||
|
"description": "",
|
||||||
|
"manual": false,
|
||||||
|
"internal": false,
|
||||||
|
"lookahead": 2,
|
||||||
|
"inputs": [
|
||||||
|
"end_event5"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"End"
|
||||||
|
],
|
||||||
|
"typename": "_EndJoin"
|
||||||
|
},
|
||||||
|
"End": {
|
||||||
|
"id": "loops_3",
|
||||||
|
"name": "End",
|
||||||
|
"description": "",
|
||||||
|
"manual": false,
|
||||||
|
"internal": false,
|
||||||
|
"lookahead": 2,
|
||||||
|
"inputs": [
|
||||||
|
"loops.EndJoin"
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"typename": "Simple"
|
||||||
|
},
|
||||||
|
"StartEvent_1": {
|
||||||
|
"id": "loops_4",
|
||||||
|
"name": "StartEvent_1",
|
||||||
|
"description": null,
|
||||||
|
"manual": false,
|
||||||
|
"internal": false,
|
||||||
|
"lookahead": 2,
|
||||||
|
"inputs": [
|
||||||
|
"Start"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"initialize"
|
||||||
|
],
|
||||||
|
"lane": null,
|
||||||
|
"documentation": null,
|
||||||
|
"position": {
|
||||||
|
"x": 152.0,
|
||||||
|
"y": 159.0
|
||||||
|
},
|
||||||
|
"data_input_associations": [],
|
||||||
|
"data_output_associations": [],
|
||||||
|
"io_specification": null,
|
||||||
|
"event_definition": {
|
||||||
|
"internal": false,
|
||||||
|
"external": false,
|
||||||
|
"typename": "NoneEventDefinition"
|
||||||
|
},
|
||||||
|
"typename": "StartEvent",
|
||||||
|
"extensions": {}
|
||||||
|
},
|
||||||
|
"initialize": {
|
||||||
|
"id": "loops_5",
|
||||||
|
"name": "initialize",
|
||||||
|
"description": "initialize",
|
||||||
|
"manual": false,
|
||||||
|
"internal": false,
|
||||||
|
"lookahead": 2,
|
||||||
|
"inputs": [
|
||||||
|
"StartEvent_1"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"TIMER_EVENT"
|
||||||
|
],
|
||||||
|
"lane": null,
|
||||||
|
"documentation": null,
|
||||||
|
"position": {
|
||||||
|
"x": 250.0,
|
||||||
|
"y": 137.0
|
||||||
|
},
|
||||||
|
"data_input_associations": [],
|
||||||
|
"data_output_associations": [],
|
||||||
|
"io_specification": null,
|
||||||
|
"script": "counter = 0",
|
||||||
|
"typename": "ScriptTask",
|
||||||
|
"extensions": {}
|
||||||
|
},
|
||||||
|
"TIMER_EVENT": {
|
||||||
|
"id": "loops_6",
|
||||||
|
"name": "TIMER_EVENT",
|
||||||
|
"description": "Wait",
|
||||||
|
"manual": false,
|
||||||
|
"internal": false,
|
||||||
|
"lookahead": 2,
|
||||||
|
"inputs": [
|
||||||
|
"Gateway_over_20",
|
||||||
|
"initialize"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"increment_counter"
|
||||||
|
],
|
||||||
|
"lane": null,
|
||||||
|
"documentation": null,
|
||||||
|
"position": {
|
||||||
|
"x": 412.0,
|
||||||
|
"y": 159.0
|
||||||
|
},
|
||||||
|
"data_input_associations": [],
|
||||||
|
"data_output_associations": [],
|
||||||
|
"io_specification": null,
|
||||||
|
"event_definition": {
|
||||||
|
"internal": true,
|
||||||
|
"external": true,
|
||||||
|
"name": "Wait",
|
||||||
|
"expression": "\"PT.01S\"",
|
||||||
|
"typename": "DurationTimerEventDefinition"
|
||||||
|
},
|
||||||
|
"typename": "IntermediateCatchEvent",
|
||||||
|
"extensions": {}
|
||||||
|
},
|
||||||
|
"increment_counter": {
|
||||||
|
"id": "loops_7",
|
||||||
|
"name": "increment_counter",
|
||||||
|
"description": "increment counter",
|
||||||
|
"manual": false,
|
||||||
|
"internal": false,
|
||||||
|
"lookahead": 2,
|
||||||
|
"inputs": [
|
||||||
|
"TIMER_EVENT"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"Activity_0w5u4k4"
|
||||||
|
],
|
||||||
|
"lane": null,
|
||||||
|
"documentation": null,
|
||||||
|
"position": {
|
||||||
|
"x": 480.0,
|
||||||
|
"y": 137.0
|
||||||
|
},
|
||||||
|
"data_input_associations": [],
|
||||||
|
"data_output_associations": [],
|
||||||
|
"io_specification": null,
|
||||||
|
"script": "counter = counter + 1",
|
||||||
|
"typename": "ScriptTask",
|
||||||
|
"extensions": {}
|
||||||
|
},
|
||||||
|
"Activity_0w5u4k4": {
|
||||||
|
"id": "loops_8",
|
||||||
|
"name": "Activity_0w5u4k4",
|
||||||
|
"description": "call something",
|
||||||
|
"manual": false,
|
||||||
|
"internal": false,
|
||||||
|
"lookahead": 2,
|
||||||
|
"inputs": [
|
||||||
|
"increment_counter"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"Gateway_over_20"
|
||||||
|
],
|
||||||
|
"lane": null,
|
||||||
|
"documentation": null,
|
||||||
|
"position": {
|
||||||
|
"x": 620.0,
|
||||||
|
"y": 137.0
|
||||||
|
},
|
||||||
|
"data_input_associations": [],
|
||||||
|
"data_output_associations": [],
|
||||||
|
"io_specification": null,
|
||||||
|
"spec": "loops_ca",
|
||||||
|
"typename": "CallActivity",
|
||||||
|
"extensions": {}
|
||||||
|
},
|
||||||
|
"Gateway_over_20": {
|
||||||
|
"id": "loops_9",
|
||||||
|
"name": "Gateway_over_20",
|
||||||
|
"description": "is > 20",
|
||||||
|
"manual": false,
|
||||||
|
"internal": false,
|
||||||
|
"lookahead": 2,
|
||||||
|
"inputs": [
|
||||||
|
"Activity_0w5u4k4"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"end_event5",
|
||||||
|
"TIMER_EVENT"
|
||||||
|
],
|
||||||
|
"lane": null,
|
||||||
|
"documentation": null,
|
||||||
|
"position": {
|
||||||
|
"x": 755.0,
|
||||||
|
"y": 152.0
|
||||||
|
},
|
||||||
|
"data_input_associations": [],
|
||||||
|
"data_output_associations": [],
|
||||||
|
"io_specification": null,
|
||||||
|
"cond_task_specs": [
|
||||||
|
{
|
||||||
|
"condition": "counter >= 20",
|
||||||
|
"task_spec": "end_event5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"condition": "counter < 20",
|
||||||
|
"task_spec": "TIMER_EVENT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"choice": null,
|
||||||
|
"default_task_spec": null,
|
||||||
|
"typename": "ExclusiveGateway",
|
||||||
|
"extensions": {}
|
||||||
|
},
|
||||||
|
"end_event5": {
|
||||||
|
"id": "loops_10",
|
||||||
|
"name": "end_event5",
|
||||||
|
"description": null,
|
||||||
|
"manual": false,
|
||||||
|
"internal": false,
|
||||||
|
"lookahead": 2,
|
||||||
|
"inputs": [
|
||||||
|
"Gateway_over_20"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"loops.EndJoin"
|
||||||
|
],
|
||||||
|
"lane": null,
|
||||||
|
"documentation": "### Results\nSubmission for Pre-Review was sent to the HSR-IRB on {{ sent_local_date_str }} at {{ sent_local_time_str }}.\n\nThe HSR-IRB started the Pre-Review process on {{ end_local_date_str }} at {{ end_local_time_str }} and assigned {{ irb_info.IRB_ADMINISTRATIVE_REVIEWER }} as the reviewer.\n\n### Metrics\n\n\nDays elapsed: {{days_delta }}",
|
||||||
|
"position": {
|
||||||
|
"x": 932.0,
|
||||||
|
"y": 159.0
|
||||||
|
},
|
||||||
|
"data_input_associations": [],
|
||||||
|
"data_output_associations": [],
|
||||||
|
"io_specification": null,
|
||||||
|
"event_definition": {
|
||||||
|
"internal": false,
|
||||||
|
"external": false,
|
||||||
|
"typename": "NoneEventDefinition"
|
||||||
|
},
|
||||||
|
"typename": "EndEvent",
|
||||||
|
"extensions": {}
|
||||||
|
},
|
||||||
|
"Root": {
|
||||||
|
"id": "loops_11",
|
||||||
|
"name": "Root",
|
||||||
|
"description": "",
|
||||||
|
"manual": false,
|
||||||
|
"internal": false,
|
||||||
|
"lookahead": 2,
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [],
|
||||||
|
"typename": "Simple"
|
||||||
|
},
|
||||||
|
"return_to_TIMER_EVENT": {
|
||||||
|
"id": "loops_12",
|
||||||
|
"name": "return_to_TIMER_EVENT",
|
||||||
|
"description": "",
|
||||||
|
"manual": false,
|
||||||
|
"internal": false,
|
||||||
|
"lookahead": 2,
|
||||||
|
"inputs": [
|
||||||
|
"Gateway_over_20",
|
||||||
|
"initialize"
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"destination_id": "b5200074-8196-40d3-8f83-204cf132856c",
|
||||||
|
"destination_spec_name": "TIMER_EVENT",
|
||||||
|
"typename": "LoopResetTask"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"io_specification": null,
|
||||||
|
"data_objects": {},
|
||||||
|
"correlation_keys": {},
|
||||||
|
"typename": "BpmnProcessSpec"
|
||||||
|
},
|
||||||
|
"subprocess_specs": {
|
||||||
|
"loops_ca": {
|
||||||
|
"name": "loops_ca",
|
||||||
|
"description": "loops_ca",
|
||||||
|
"file": "/home/essweine/work/sartography/code/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/too_many_loops_call_activity.bpmn",
|
||||||
|
"task_specs": {
|
||||||
|
"Start": {
|
||||||
|
"id": "loops_ca_1",
|
||||||
|
"name": "Start",
|
||||||
|
"description": "",
|
||||||
|
"manual": false,
|
||||||
|
"internal": false,
|
||||||
|
"lookahead": 2,
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
"StartEvent_1"
|
||||||
|
],
|
||||||
|
"typename": "StartTask"
|
||||||
|
},
|
||||||
|
"loops_ca.EndJoin": {
|
||||||
|
"id": "loops_ca_2",
|
||||||
|
"name": "loops_ca.EndJoin",
|
||||||
|
"description": "",
|
||||||
|
"manual": false,
|
||||||
|
"internal": false,
|
||||||
|
"lookahead": 2,
|
||||||
|
"inputs": [
|
||||||
|
"end_event5"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"End"
|
||||||
|
],
|
||||||
|
"typename": "_EndJoin"
|
||||||
|
},
|
||||||
|
"End": {
|
||||||
|
"id": "loops_ca_3",
|
||||||
|
"name": "End",
|
||||||
|
"description": "",
|
||||||
|
"manual": false,
|
||||||
|
"internal": false,
|
||||||
|
"lookahead": 2,
|
||||||
|
"inputs": [
|
||||||
|
"loops_ca.EndJoin"
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"typename": "Simple"
|
||||||
|
},
|
||||||
|
"StartEvent_1": {
|
||||||
|
"id": "loops_ca_4",
|
||||||
|
"name": "StartEvent_1",
|
||||||
|
"description": null,
|
||||||
|
"manual": false,
|
||||||
|
"internal": false,
|
||||||
|
"lookahead": 2,
|
||||||
|
"inputs": [
|
||||||
|
"Start"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"increment_counter"
|
||||||
|
],
|
||||||
|
"lane": null,
|
||||||
|
"documentation": null,
|
||||||
|
"position": {
|
||||||
|
"x": 152.0,
|
||||||
|
"y": 109.0
|
||||||
|
},
|
||||||
|
"data_input_associations": [],
|
||||||
|
"data_output_associations": [],
|
||||||
|
"io_specification": null,
|
||||||
|
"event_definition": {
|
||||||
|
"internal": false,
|
||||||
|
"external": false,
|
||||||
|
"typename": "NoneEventDefinition"
|
||||||
|
},
|
||||||
|
"typename": "StartEvent",
|
||||||
|
"extensions": {}
|
||||||
|
},
|
||||||
|
"increment_counter": {
|
||||||
|
"id": "loops_ca_5",
|
||||||
|
"name": "increment_counter",
|
||||||
|
"description": "increment counter",
|
||||||
|
"manual": false,
|
||||||
|
"internal": false,
|
||||||
|
"lookahead": 2,
|
||||||
|
"inputs": [
|
||||||
|
"StartEvent_1"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"end_event5"
|
||||||
|
],
|
||||||
|
"lane": null,
|
||||||
|
"documentation": null,
|
||||||
|
"position": {
|
||||||
|
"x": 220.0,
|
||||||
|
"y": 87.0
|
||||||
|
},
|
||||||
|
"data_input_associations": [],
|
||||||
|
"data_output_associations": [],
|
||||||
|
"io_specification": null,
|
||||||
|
"script": "counter2 = 1000",
|
||||||
|
"typename": "ScriptTask",
|
||||||
|
"extensions": {}
|
||||||
|
},
|
||||||
|
"end_event5": {
|
||||||
|
"id": "loops_ca_6",
|
||||||
|
"name": "end_event5",
|
||||||
|
"description": null,
|
||||||
|
"manual": false,
|
||||||
|
"internal": false,
|
||||||
|
"lookahead": 2,
|
||||||
|
"inputs": [
|
||||||
|
"increment_counter"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"loops_ca.EndJoin"
|
||||||
|
],
|
||||||
|
"lane": null,
|
||||||
|
"documentation": "### Results\nSubmission for Pre-Review was sent to the HSR-IRB on {{ sent_local_date_str }} at {{ sent_local_time_str }}.\n\nThe HSR-IRB started the Pre-Review process on {{ end_local_date_str }} at {{ end_local_time_str }} and assigned {{ irb_info.IRB_ADMINISTRATIVE_REVIEWER }} as the reviewer.\n\n### Metrics\n\n\nDays elapsed: {{days_delta }}",
|
||||||
|
"position": {
|
||||||
|
"x": 362.0,
|
||||||
|
"y": 109.0
|
||||||
|
},
|
||||||
|
"data_input_associations": [],
|
||||||
|
"data_output_associations": [],
|
||||||
|
"io_specification": null,
|
||||||
|
"event_definition": {
|
||||||
|
"internal": false,
|
||||||
|
"external": false,
|
||||||
|
"typename": "NoneEventDefinition"
|
||||||
|
},
|
||||||
|
"typename": "EndEvent",
|
||||||
|
"extensions": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"io_specification": null,
|
||||||
|
"data_objects": {},
|
||||||
|
"correlation_keys": {},
|
||||||
|
"typename": "BpmnProcessSpec"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"subprocesses": {},
|
||||||
|
"bpmn_messages": [],
|
||||||
|
"correlations": {}
|
||||||
|
}
|
@ -1,88 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_0hx6iqe" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.11.1" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.15.0">
|
|
||||||
<bpmn:process id="main" name="Main" isExecutable="true">
|
|
||||||
<bpmn:startEvent id="StartEvent_1">
|
|
||||||
<bpmn:outgoing>Flow_0nlj5lh</bpmn:outgoing>
|
|
||||||
</bpmn:startEvent>
|
|
||||||
<bpmn:task id="initialize" name="Initialize">
|
|
||||||
<bpmn:incoming>Flow_0nlj5lh</bpmn:incoming>
|
|
||||||
<bpmn:incoming>Flow_16vai1a</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_1lkecht</bpmn:outgoing>
|
|
||||||
</bpmn:task>
|
|
||||||
<bpmn:sequenceFlow id="Flow_1lkecht" sourceRef="initialize" targetRef="subprocess_in_loop" />
|
|
||||||
<bpmn:callActivity id="subprocess_in_loop" name="Subprocess in Loop" calledElement="subprocess">
|
|
||||||
<bpmn:incoming>Flow_1lkecht</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_1vci114</bpmn:outgoing>
|
|
||||||
</bpmn:callActivity>
|
|
||||||
<bpmn:sequenceFlow id="Flow_1vci114" sourceRef="subprocess_in_loop" targetRef="call_2" />
|
|
||||||
<bpmn:exclusiveGateway id="Gateway_158gdvg" default="Flow_16vai1a">
|
|
||||||
<bpmn:incoming>Flow_0iui938</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_0ew7zdi</bpmn:outgoing>
|
|
||||||
<bpmn:outgoing>Flow_16vai1a</bpmn:outgoing>
|
|
||||||
</bpmn:exclusiveGateway>
|
|
||||||
<bpmn:endEvent id="Event_0l6q7ei">
|
|
||||||
<bpmn:incoming>Flow_0ew7zdi</bpmn:incoming>
|
|
||||||
</bpmn:endEvent>
|
|
||||||
<bpmn:sequenceFlow id="Flow_0ew7zdi" sourceRef="Gateway_158gdvg" targetRef="Event_0l6q7ei">
|
|
||||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">done</bpmn:conditionExpression>
|
|
||||||
</bpmn:sequenceFlow>
|
|
||||||
<bpmn:sequenceFlow id="Flow_0iui938" sourceRef="call_2" targetRef="Gateway_158gdvg" />
|
|
||||||
<bpmn:callActivity id="call_2" name="Call Subprocess again" calledElement="subprocess">
|
|
||||||
<bpmn:incoming>Flow_1vci114</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_0iui938</bpmn:outgoing>
|
|
||||||
</bpmn:callActivity>
|
|
||||||
<bpmn:sequenceFlow id="Flow_0nlj5lh" sourceRef="StartEvent_1" targetRef="initialize" />
|
|
||||||
<bpmn:sequenceFlow id="Flow_16vai1a" sourceRef="Gateway_158gdvg" targetRef="initialize" />
|
|
||||||
</bpmn:process>
|
|
||||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
|
||||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="main">
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1lkecht_di" bpmnElement="Flow_1lkecht">
|
|
||||||
<di:waypoint x="350" y="117" />
|
|
||||||
<di:waypoint x="420" y="117" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1vci114_di" bpmnElement="Flow_1vci114">
|
|
||||||
<di:waypoint x="520" y="117" />
|
|
||||||
<di:waypoint x="580" y="117" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_0ew7zdi_di" bpmnElement="Flow_0ew7zdi">
|
|
||||||
<di:waypoint x="785" y="117" />
|
|
||||||
<di:waypoint x="862" y="117" />
|
|
||||||
<bpmndi:BPMNLabel>
|
|
||||||
<dc:Bounds x="1104" y="159" width="60" height="14" />
|
|
||||||
</bpmndi:BPMNLabel>
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_0iui938_di" bpmnElement="Flow_0iui938">
|
|
||||||
<di:waypoint x="680" y="117" />
|
|
||||||
<di:waypoint x="735" y="117" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_0nlj5lh_di" bpmnElement="Flow_0nlj5lh">
|
|
||||||
<di:waypoint x="188" y="117" />
|
|
||||||
<di:waypoint x="250" y="117" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_16vai1a_di" bpmnElement="Flow_16vai1a">
|
|
||||||
<di:waypoint x="760" y="142" />
|
|
||||||
<di:waypoint x="760" y="240" />
|
|
||||||
<di:waypoint x="300" y="240" />
|
|
||||||
<di:waypoint x="300" y="157" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNShape id="Activity_0721i5u_di" bpmnElement="initialize">
|
|
||||||
<dc:Bounds x="250" y="77" width="100" height="80" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Activity_1lcptwy_di" bpmnElement="call_2">
|
|
||||||
<dc:Bounds x="580" y="77" width="100" height="80" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
|
||||||
<dc:Bounds x="152" y="99" width="36" height="36" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Activity_1stre5m_di" bpmnElement="subprocess_in_loop">
|
|
||||||
<dc:Bounds x="420" y="77" width="100" height="80" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Gateway_158gdvg_di" bpmnElement="Gateway_158gdvg" isMarkerVisible="true">
|
|
||||||
<dc:Bounds x="735" y="92" width="50" height="50" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Event_0l6q7ei_di" bpmnElement="Event_0l6q7ei">
|
|
||||||
<dc:Bounds x="862" y="99" width="36" height="36" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
</bpmndi:BPMNPlane>
|
|
||||||
</bpmndi:BPMNDiagram>
|
|
||||||
</bpmn:definitions>
|
|
@ -1,38 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_0vwjlip" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.11.1" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.15.0">
|
|
||||||
<bpmn:process id="subprocess" name="Subprocess" isExecutable="true">
|
|
||||||
<bpmn:startEvent id="StartEvent_1">
|
|
||||||
<bpmn:outgoing>Flow_1dbtwxp</bpmn:outgoing>
|
|
||||||
</bpmn:startEvent>
|
|
||||||
<bpmn:sequenceFlow id="Flow_1dbtwxp" sourceRef="StartEvent_1" targetRef="Activity_1g5e8v7" />
|
|
||||||
<bpmn:endEvent id="Event_1ukgnws">
|
|
||||||
<bpmn:incoming>Flow_1t99mly</bpmn:incoming>
|
|
||||||
</bpmn:endEvent>
|
|
||||||
<bpmn:sequenceFlow id="Flow_1t99mly" sourceRef="Activity_1g5e8v7" targetRef="Event_1ukgnws" />
|
|
||||||
<bpmn:task id="Activity_1g5e8v7" name="Subprocess Task">
|
|
||||||
<bpmn:incoming>Flow_1dbtwxp</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_1t99mly</bpmn:outgoing>
|
|
||||||
</bpmn:task>
|
|
||||||
</bpmn:process>
|
|
||||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
|
||||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="subprocess">
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1dbtwxp_di" bpmnElement="Flow_1dbtwxp">
|
|
||||||
<di:waypoint x="215" y="117" />
|
|
||||||
<di:waypoint x="270" y="117" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1t99mly_di" bpmnElement="Flow_1t99mly">
|
|
||||||
<di:waypoint x="370" y="117" />
|
|
||||||
<di:waypoint x="432" y="117" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
|
||||||
<dc:Bounds x="179" y="99" width="36" height="36" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Event_1ukgnws_di" bpmnElement="Event_1ukgnws">
|
|
||||||
<dc:Bounds x="432" y="99" width="36" height="36" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Activity_0hql87y_di" bpmnElement="Activity_1g5e8v7">
|
|
||||||
<dc:Bounds x="270" y="77" width="100" height="80" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
</bpmndi:BPMNPlane>
|
|
||||||
</bpmndi:BPMNDiagram>
|
|
||||||
</bpmn:definitions>
|
|
@ -1,125 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_d4f3442" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.11.1">
|
|
||||||
<bpmn:process id="loops" isExecutable="true">
|
|
||||||
<bpmn:scriptTask id="increment_counter" name="increment counter">
|
|
||||||
<bpmn:incoming>Flow_1gb8wca</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_1d2usdq</bpmn:outgoing>
|
|
||||||
<bpmn:script>counter = counter + 1</bpmn:script>
|
|
||||||
</bpmn:scriptTask>
|
|
||||||
<bpmn:sequenceFlow id="Flow_1d2usdq" sourceRef="increment_counter" targetRef="Activity_0w5u4k4" />
|
|
||||||
<bpmn:endEvent id="end_event5">
|
|
||||||
<bpmn:documentation>### Results
|
|
||||||
Submission for Pre-Review was sent to the HSR-IRB on {{ sent_local_date_str }} at {{ sent_local_time_str }}.
|
|
||||||
|
|
||||||
The HSR-IRB started the Pre-Review process on {{ end_local_date_str }} at {{ end_local_time_str }} and assigned {{ irb_info.IRB_ADMINISTRATIVE_REVIEWER }} as the reviewer.
|
|
||||||
|
|
||||||
### Metrics
|
|
||||||
|
|
||||||
|
|
||||||
Days elapsed: {{days_delta }}</bpmn:documentation>
|
|
||||||
<bpmn:incoming>Flow_1tj9oz1</bpmn:incoming>
|
|
||||||
</bpmn:endEvent>
|
|
||||||
<bpmn:sequenceFlow id="Flow_1gb8wca" sourceRef="TIMER_EVENT" targetRef="increment_counter" />
|
|
||||||
<bpmn:intermediateCatchEvent id="TIMER_EVENT" name="Wait">
|
|
||||||
<bpmn:incoming>Flow_15jw6a4</bpmn:incoming>
|
|
||||||
<bpmn:incoming>Flow_0op1a19</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_1gb8wca</bpmn:outgoing>
|
|
||||||
<bpmn:timerEventDefinition id="TimerEventDefinition_0x6divu">
|
|
||||||
<bpmn:timeDuration xsi:type="bpmn:tFormalExpression">"PT.01S"</bpmn:timeDuration>
|
|
||||||
</bpmn:timerEventDefinition>
|
|
||||||
</bpmn:intermediateCatchEvent>
|
|
||||||
<bpmn:exclusiveGateway id="Gateway_over_20" name="is > 20">
|
|
||||||
<bpmn:incoming>Flow_0mxlkif</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_1tj9oz1</bpmn:outgoing>
|
|
||||||
<bpmn:outgoing>Flow_0op1a19</bpmn:outgoing>
|
|
||||||
</bpmn:exclusiveGateway>
|
|
||||||
<bpmn:sequenceFlow id="Flow_1tj9oz1" name="Yes" sourceRef="Gateway_over_20" targetRef="end_event5">
|
|
||||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">counter >= 20</bpmn:conditionExpression>
|
|
||||||
</bpmn:sequenceFlow>
|
|
||||||
<bpmn:sequenceFlow id="Flow_0op1a19" name="No" sourceRef="Gateway_over_20" targetRef="TIMER_EVENT">
|
|
||||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">counter < 20</bpmn:conditionExpression>
|
|
||||||
</bpmn:sequenceFlow>
|
|
||||||
<bpmn:sequenceFlow id="Flow_15jw6a4" sourceRef="initialize" targetRef="TIMER_EVENT" />
|
|
||||||
<bpmn:scriptTask id="initialize" name="initialize">
|
|
||||||
<bpmn:incoming>Flow_0q7fkb7</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_15jw6a4</bpmn:outgoing>
|
|
||||||
<bpmn:script>counter = 0</bpmn:script>
|
|
||||||
</bpmn:scriptTask>
|
|
||||||
<bpmn:sequenceFlow id="Flow_0mxlkif" sourceRef="Activity_0w5u4k4" targetRef="Gateway_over_20" />
|
|
||||||
<bpmn:callActivity id="Activity_0w5u4k4" name="call something" calledElement="loops_ca">
|
|
||||||
<bpmn:incoming>Flow_1d2usdq</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_0mxlkif</bpmn:outgoing>
|
|
||||||
</bpmn:callActivity>
|
|
||||||
<bpmn:startEvent id="StartEvent_1">
|
|
||||||
<bpmn:outgoing>Flow_0q7fkb7</bpmn:outgoing>
|
|
||||||
</bpmn:startEvent>
|
|
||||||
<bpmn:sequenceFlow id="Flow_0q7fkb7" sourceRef="StartEvent_1" targetRef="initialize" />
|
|
||||||
</bpmn:process>
|
|
||||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
|
||||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="loops">
|
|
||||||
<bpmndi:BPMNEdge id="Flow_0q7fkb7_di" bpmnElement="Flow_0q7fkb7">
|
|
||||||
<di:waypoint x="188" y="177" />
|
|
||||||
<di:waypoint x="250" y="177" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_0mxlkif_di" bpmnElement="Flow_0mxlkif">
|
|
||||||
<di:waypoint x="720" y="177" />
|
|
||||||
<di:waypoint x="755" y="177" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_15jw6a4_di" bpmnElement="Flow_15jw6a4">
|
|
||||||
<di:waypoint x="350" y="177" />
|
|
||||||
<di:waypoint x="412" y="177" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_0op1a19_di" bpmnElement="Flow_0op1a19">
|
|
||||||
<di:waypoint x="780" y="152" />
|
|
||||||
<di:waypoint x="780" y="80" />
|
|
||||||
<di:waypoint x="430" y="80" />
|
|
||||||
<di:waypoint x="430" y="159" />
|
|
||||||
<bpmndi:BPMNLabel>
|
|
||||||
<dc:Bounds x="598" y="62" width="15" height="14" />
|
|
||||||
</bpmndi:BPMNLabel>
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1tj9oz1_di" bpmnElement="Flow_1tj9oz1">
|
|
||||||
<di:waypoint x="805" y="177" />
|
|
||||||
<di:waypoint x="932" y="177" />
|
|
||||||
<bpmndi:BPMNLabel>
|
|
||||||
<dc:Bounds x="818" y="159" width="19" height="14" />
|
|
||||||
</bpmndi:BPMNLabel>
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1gb8wca_di" bpmnElement="Flow_1gb8wca">
|
|
||||||
<di:waypoint x="448" y="177" />
|
|
||||||
<di:waypoint x="480" y="177" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1d2usdq_di" bpmnElement="Flow_1d2usdq">
|
|
||||||
<di:waypoint x="580" y="177" />
|
|
||||||
<di:waypoint x="620" y="177" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNShape id="Activity_1fgfg5d_di" bpmnElement="increment_counter">
|
|
||||||
<dc:Bounds x="480" y="137" width="100" height="80" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Event_1tseamj_di" bpmnElement="end_event5">
|
|
||||||
<dc:Bounds x="932" y="159" width="36" height="36" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Event_0whindt_di" bpmnElement="TIMER_EVENT">
|
|
||||||
<dc:Bounds x="412" y="159" width="36" height="36" />
|
|
||||||
<bpmndi:BPMNLabel>
|
|
||||||
<dc:Bounds x="419" y="202" width="22" height="14" />
|
|
||||||
</bpmndi:BPMNLabel>
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Gateway_0gr4bqq_di" bpmnElement="Gateway_over_20" isMarkerVisible="true">
|
|
||||||
<dc:Bounds x="755" y="152" width="50" height="50" />
|
|
||||||
<bpmndi:BPMNLabel>
|
|
||||||
<dc:Bounds x="768" y="209" width="33" height="14" />
|
|
||||||
</bpmndi:BPMNLabel>
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Activity_0whalo9_di" bpmnElement="initialize">
|
|
||||||
<dc:Bounds x="250" y="137" width="100" height="80" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Activity_0mmgsiw_di" bpmnElement="Activity_0w5u4k4">
|
|
||||||
<dc:Bounds x="620" y="137" width="100" height="80" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
|
||||||
<dc:Bounds x="152" y="159" width="36" height="36" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
</bpmndi:BPMNPlane>
|
|
||||||
</bpmndi:BPMNDiagram>
|
|
||||||
</bpmn:definitions>
|
|
@ -1,48 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_d4f3442" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.10.0">
|
|
||||||
<bpmn:process id="loops_ca" isExecutable="true">
|
|
||||||
<bpmn:startEvent id="StartEvent_1">
|
|
||||||
<bpmn:outgoing>Flow_175n91v</bpmn:outgoing>
|
|
||||||
</bpmn:startEvent>
|
|
||||||
<bpmn:scriptTask id="increment_counter" name="increment counter">
|
|
||||||
<bpmn:incoming>Flow_175n91v</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_1d2usdq</bpmn:outgoing>
|
|
||||||
<bpmn:script>counter2 = 1000</bpmn:script>
|
|
||||||
</bpmn:scriptTask>
|
|
||||||
<bpmn:sequenceFlow id="Flow_1d2usdq" sourceRef="increment_counter" targetRef="end_event5" />
|
|
||||||
<bpmn:endEvent id="end_event5">
|
|
||||||
<bpmn:documentation>### Results
|
|
||||||
Submission for Pre-Review was sent to the HSR-IRB on {{ sent_local_date_str }} at {{ sent_local_time_str }}.
|
|
||||||
|
|
||||||
The HSR-IRB started the Pre-Review process on {{ end_local_date_str }} at {{ end_local_time_str }} and assigned {{ irb_info.IRB_ADMINISTRATIVE_REVIEWER }} as the reviewer.
|
|
||||||
|
|
||||||
### Metrics
|
|
||||||
|
|
||||||
|
|
||||||
Days elapsed: {{days_delta }}</bpmn:documentation>
|
|
||||||
<bpmn:incoming>Flow_1d2usdq</bpmn:incoming>
|
|
||||||
</bpmn:endEvent>
|
|
||||||
<bpmn:sequenceFlow id="Flow_175n91v" sourceRef="StartEvent_1" targetRef="increment_counter" />
|
|
||||||
</bpmn:process>
|
|
||||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
|
||||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="loops_ca">
|
|
||||||
<bpmndi:BPMNEdge id="Flow_175n91v_di" bpmnElement="Flow_175n91v">
|
|
||||||
<di:waypoint x="188" y="127" />
|
|
||||||
<di:waypoint x="220" y="127" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1d2usdq_di" bpmnElement="Flow_1d2usdq">
|
|
||||||
<di:waypoint x="320" y="127" />
|
|
||||||
<di:waypoint x="362" y="127" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
|
||||||
<dc:Bounds x="152" y="109" width="36" height="36" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Activity_1fgfg5d_di" bpmnElement="increment_counter">
|
|
||||||
<dc:Bounds x="220" y="87" width="100" height="80" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Event_1tseamj_di" bpmnElement="end_event5">
|
|
||||||
<dc:Bounds x="362" y="109" width="36" height="36" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
</bpmndi:BPMNPlane>
|
|
||||||
</bpmndi:BPMNDiagram>
|
|
||||||
</bpmn:definitions>
|
|
@ -1,157 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_d4f3442" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.11.1">
|
|
||||||
<bpmn:process id="loops_sub" isExecutable="true">
|
|
||||||
<bpmn:startEvent id="StartEvent_1">
|
|
||||||
<bpmn:outgoing>Flow_0q7fkb7</bpmn:outgoing>
|
|
||||||
</bpmn:startEvent>
|
|
||||||
<bpmn:sequenceFlow id="Flow_0q7fkb7" sourceRef="StartEvent_1" targetRef="initialize" />
|
|
||||||
<bpmn:scriptTask id="increment_counter" name="increment counter">
|
|
||||||
<bpmn:incoming>Flow_1gb8wca</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_1d2usdq</bpmn:outgoing>
|
|
||||||
<bpmn:script>counter = counter + 1</bpmn:script>
|
|
||||||
</bpmn:scriptTask>
|
|
||||||
<bpmn:sequenceFlow id="Flow_1d2usdq" sourceRef="increment_counter" targetRef="Gateway_over_20" />
|
|
||||||
<bpmn:endEvent id="end_event5">
|
|
||||||
<bpmn:documentation>### Results
|
|
||||||
Submission for Pre-Review was sent to the HSR-IRB on {{ sent_local_date_str }} at {{ sent_local_time_str }}.
|
|
||||||
|
|
||||||
The HSR-IRB started the Pre-Review process on {{ end_local_date_str }} at {{ end_local_time_str }} and assigned {{ irb_info.IRB_ADMINISTRATIVE_REVIEWER }} as the reviewer.
|
|
||||||
|
|
||||||
### Metrics
|
|
||||||
|
|
||||||
|
|
||||||
Days elapsed: {{days_delta }}</bpmn:documentation>
|
|
||||||
<bpmn:incoming>Flow_1tj9oz1</bpmn:incoming>
|
|
||||||
</bpmn:endEvent>
|
|
||||||
<bpmn:sequenceFlow id="Flow_1gb8wca" sourceRef="TIMER_EVENT" targetRef="increment_counter" />
|
|
||||||
<bpmn:intermediateCatchEvent id="TIMER_EVENT" name="Wait">
|
|
||||||
<bpmn:incoming>Flow_15jw6a4</bpmn:incoming>
|
|
||||||
<bpmn:incoming>Flow_1ivr6d7</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_1gb8wca</bpmn:outgoing>
|
|
||||||
<bpmn:timerEventDefinition id="TimerEventDefinition_0x6divu">
|
|
||||||
<bpmn:timeDuration xsi:type="bpmn:tFormalExpression">"PT0.01S"</bpmn:timeDuration>
|
|
||||||
</bpmn:timerEventDefinition>
|
|
||||||
</bpmn:intermediateCatchEvent>
|
|
||||||
<bpmn:exclusiveGateway id="Gateway_over_20" name="is > 20">
|
|
||||||
<bpmn:incoming>Flow_1d2usdq</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_1tj9oz1</bpmn:outgoing>
|
|
||||||
<bpmn:outgoing>Flow_0op1a19</bpmn:outgoing>
|
|
||||||
</bpmn:exclusiveGateway>
|
|
||||||
<bpmn:sequenceFlow id="Flow_1tj9oz1" name="Yes" sourceRef="Gateway_over_20" targetRef="end_event5">
|
|
||||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">counter >= 20</bpmn:conditionExpression>
|
|
||||||
</bpmn:sequenceFlow>
|
|
||||||
<bpmn:sequenceFlow id="Flow_0op1a19" name="No" sourceRef="Gateway_over_20" targetRef="my_sub_process">
|
|
||||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">counter < 20</bpmn:conditionExpression>
|
|
||||||
</bpmn:sequenceFlow>
|
|
||||||
<bpmn:sequenceFlow id="Flow_15jw6a4" sourceRef="initialize" targetRef="TIMER_EVENT" />
|
|
||||||
<bpmn:scriptTask id="initialize" name="initialize">
|
|
||||||
<bpmn:incoming>Flow_0q7fkb7</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_15jw6a4</bpmn:outgoing>
|
|
||||||
<bpmn:script>counter = 0
|
|
||||||
counter2 = 0
|
|
||||||
counter3 = 0</bpmn:script>
|
|
||||||
</bpmn:scriptTask>
|
|
||||||
<bpmn:sequenceFlow id="Flow_1ivr6d7" sourceRef="my_sub_process" targetRef="TIMER_EVENT" />
|
|
||||||
<bpmn:subProcess id="my_sub_process" name="Sub Process">
|
|
||||||
<bpmn:incoming>Flow_0op1a19</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_1ivr6d7</bpmn:outgoing>
|
|
||||||
<bpmn:startEvent id="Event_0ubr6g5">
|
|
||||||
<bpmn:outgoing>Flow_1fcanuu</bpmn:outgoing>
|
|
||||||
</bpmn:startEvent>
|
|
||||||
<bpmn:sequenceFlow id="Flow_1fcanuu" sourceRef="Event_0ubr6g5" targetRef="Activity_05x1bpx" />
|
|
||||||
<bpmn:scriptTask id="Activity_05x1bpx" name="Increment Counter #2">
|
|
||||||
<bpmn:incoming>Flow_1fcanuu</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_04le6u5</bpmn:outgoing>
|
|
||||||
<bpmn:script>counter2 += 1</bpmn:script>
|
|
||||||
</bpmn:scriptTask>
|
|
||||||
<bpmn:endEvent id="Event_0umaw1x">
|
|
||||||
<bpmn:incoming>Flow_04le6u5</bpmn:incoming>
|
|
||||||
</bpmn:endEvent>
|
|
||||||
<bpmn:sequenceFlow id="Flow_04le6u5" sourceRef="Activity_05x1bpx" targetRef="Event_0umaw1x" />
|
|
||||||
</bpmn:subProcess>
|
|
||||||
</bpmn:process>
|
|
||||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
|
||||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="loops_sub">
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1ivr6d7_di" bpmnElement="Flow_1ivr6d7">
|
|
||||||
<di:waypoint x="490" y="310" />
|
|
||||||
<di:waypoint x="430" y="310" />
|
|
||||||
<di:waypoint x="430" y="145" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_15jw6a4_di" bpmnElement="Flow_15jw6a4">
|
|
||||||
<di:waypoint x="350" y="127" />
|
|
||||||
<di:waypoint x="412" y="127" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_0op1a19_di" bpmnElement="Flow_0op1a19">
|
|
||||||
<di:waypoint x="920" y="152" />
|
|
||||||
<di:waypoint x="920" y="310" />
|
|
||||||
<di:waypoint x="840" y="310" />
|
|
||||||
<bpmndi:BPMNLabel>
|
|
||||||
<dc:Bounds x="863" y="292" width="15" height="14" />
|
|
||||||
</bpmndi:BPMNLabel>
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1tj9oz1_di" bpmnElement="Flow_1tj9oz1">
|
|
||||||
<di:waypoint x="945" y="127" />
|
|
||||||
<di:waypoint x="1072" y="127" />
|
|
||||||
<bpmndi:BPMNLabel>
|
|
||||||
<dc:Bounds x="958" y="109" width="19" height="14" />
|
|
||||||
</bpmndi:BPMNLabel>
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1gb8wca_di" bpmnElement="Flow_1gb8wca">
|
|
||||||
<di:waypoint x="448" y="127" />
|
|
||||||
<di:waypoint x="625" y="127" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1d2usdq_di" bpmnElement="Flow_1d2usdq">
|
|
||||||
<di:waypoint x="725" y="127" />
|
|
||||||
<di:waypoint x="895" y="127" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_0q7fkb7_di" bpmnElement="Flow_0q7fkb7">
|
|
||||||
<di:waypoint x="188" y="127" />
|
|
||||||
<di:waypoint x="250" y="127" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
|
||||||
<dc:Bounds x="152" y="109" width="36" height="36" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Activity_1fgfg5d_di" bpmnElement="increment_counter">
|
|
||||||
<dc:Bounds x="625" y="87" width="100" height="80" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Event_1tseamj_di" bpmnElement="end_event5">
|
|
||||||
<dc:Bounds x="1072" y="109" width="36" height="36" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Event_0whindt_di" bpmnElement="TIMER_EVENT">
|
|
||||||
<dc:Bounds x="412" y="109" width="36" height="36" />
|
|
||||||
<bpmndi:BPMNLabel>
|
|
||||||
<dc:Bounds x="419" y="85" width="22" height="14" />
|
|
||||||
</bpmndi:BPMNLabel>
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Gateway_0gr4bqq_di" bpmnElement="Gateway_over_20" isMarkerVisible="true">
|
|
||||||
<dc:Bounds x="895" y="102" width="50" height="50" />
|
|
||||||
<bpmndi:BPMNLabel>
|
|
||||||
<dc:Bounds x="908" y="159" width="33" height="14" />
|
|
||||||
</bpmndi:BPMNLabel>
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Activity_0whalo9_di" bpmnElement="initialize">
|
|
||||||
<dc:Bounds x="250" y="87" width="100" height="80" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Activity_0v3bik3_di" bpmnElement="my_sub_process" isExpanded="true">
|
|
||||||
<dc:Bounds x="490" y="210" width="350" height="200" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_04le6u5_di" bpmnElement="Flow_04le6u5">
|
|
||||||
<di:waypoint x="720" y="310" />
|
|
||||||
<di:waypoint x="782" y="310" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1fcanuu_di" bpmnElement="Flow_1fcanuu">
|
|
||||||
<di:waypoint x="566" y="310" />
|
|
||||||
<di:waypoint x="620" y="310" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNShape id="Event_0ubr6g5_di" bpmnElement="Event_0ubr6g5">
|
|
||||||
<dc:Bounds x="530" y="292" width="36" height="36" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Activity_08o251p_di" bpmnElement="Activity_05x1bpx">
|
|
||||||
<dc:Bounds x="620" y="270" width="100" height="80" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Event_0umaw1x_di" bpmnElement="Event_0umaw1x">
|
|
||||||
<dc:Bounds x="782" y="292" width="36" height="36" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
</bpmndi:BPMNPlane>
|
|
||||||
</bpmndi:BPMNDiagram>
|
|
||||||
</bpmn:definitions>
|
|
@ -51,7 +51,7 @@ class CallActivityEscalationTest(BpmnWorkflowTestCase):
|
|||||||
task.set_data(should_escalate=True)
|
task.set_data(should_escalate=True)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.save_restore()
|
self.save_restore()
|
||||||
self.workflow.complete_all()
|
self.workflow.run_all()
|
||||||
self.assertEqual(True, self.workflow.is_completed())
|
self.assertEqual(True, self.workflow.is_completed())
|
||||||
|
|
||||||
self.assertEqual(True, 'EndEvent_specific1_noninterrupting_normal' in completed_set)
|
self.assertEqual(True, 'EndEvent_specific1_noninterrupting_normal' in completed_set)
|
||||||
@ -81,7 +81,7 @@ class CallActivityEscalationTest(BpmnWorkflowTestCase):
|
|||||||
task.set_data(should_escalate=False)
|
task.set_data(should_escalate=False)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.save_restore()
|
self.save_restore()
|
||||||
self.workflow.complete_all()
|
self.workflow.run_all()
|
||||||
self.assertEqual(True, self.workflow.is_completed())
|
self.assertEqual(True, self.workflow.is_completed())
|
||||||
|
|
||||||
self.assertEqual(True, 'EndEvent_specific1_noninterrupting_normal' in completed_set)
|
self.assertEqual(True, 'EndEvent_specific1_noninterrupting_normal' in completed_set)
|
||||||
@ -109,7 +109,7 @@ class CallActivityEscalationTest(BpmnWorkflowTestCase):
|
|||||||
track_workflow(self.spec, completed_set)
|
track_workflow(self.spec, completed_set)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.save_restore()
|
self.save_restore()
|
||||||
self.workflow.complete_all()
|
self.workflow.run_all()
|
||||||
self.assertEqual(True, self.workflow.is_completed())
|
self.assertEqual(True, self.workflow.is_completed())
|
||||||
|
|
||||||
self.assertEqual(True, 'EndEvent_specific1_noninterrupting_normal' in completed_set)
|
self.assertEqual(True, 'EndEvent_specific1_noninterrupting_normal' in completed_set)
|
||||||
|
@ -28,7 +28,7 @@ class EventBasedGatewayTest(BpmnWorkflowTestCase):
|
|||||||
if save_restore:
|
if save_restore:
|
||||||
self.save_restore()
|
self.save_restore()
|
||||||
self.workflow.script_engine = self.script_engine
|
self.workflow.script_engine = self.script_engine
|
||||||
self.assertEqual(len(waiting_tasks), 1)
|
self.assertEqual(len(waiting_tasks), 2)
|
||||||
self.workflow.catch(MessageEventDefinition('message_1'))
|
self.workflow.catch(MessageEventDefinition('message_1'))
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.workflow.refresh_waiting_tasks()
|
self.workflow.refresh_waiting_tasks()
|
||||||
@ -41,7 +41,7 @@ class EventBasedGatewayTest(BpmnWorkflowTestCase):
|
|||||||
|
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
waiting_tasks = self.workflow.get_waiting_tasks()
|
waiting_tasks = self.workflow.get_waiting_tasks()
|
||||||
self.assertEqual(len(waiting_tasks), 1)
|
self.assertEqual(len(waiting_tasks), 2)
|
||||||
timer_event = waiting_tasks[0].task_spec.event_definition.event_definitions[-1]
|
timer_event = waiting_tasks[0].task_spec.event_definition.event_definitions[-1]
|
||||||
self.workflow.catch(timer_event)
|
self.workflow.catch(timer_event)
|
||||||
self.workflow.refresh_waiting_tasks()
|
self.workflow.refresh_waiting_tasks()
|
||||||
|
@ -33,7 +33,7 @@ class MultipleEventsTest(BpmnWorkflowTestCase):
|
|||||||
task = self.workflow.get_tasks(TaskState.READY)[0]
|
task = self.workflow.get_tasks(TaskState.READY)[0]
|
||||||
|
|
||||||
# Move to User Task 1
|
# Move to User Task 1
|
||||||
self.workflow.complete_task_from_id(task.id)
|
self.workflow.run_task_from_id(task.id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
task = self.workflow.get_tasks(TaskState.READY)[0]
|
task = self.workflow.get_tasks(TaskState.READY)[0]
|
||||||
self.assertEqual('UserTaskOne', task.get_name())
|
self.assertEqual('UserTaskOne', task.get_name())
|
||||||
@ -52,10 +52,10 @@ class MultipleEventsTest(BpmnWorkflowTestCase):
|
|||||||
task = self.workflow.get_tasks(TaskState.READY)[0]
|
task = self.workflow.get_tasks(TaskState.READY)[0]
|
||||||
|
|
||||||
# Move to User Task 2
|
# Move to User Task 2
|
||||||
self.workflow.complete_task_from_id(task.id)
|
self.workflow.run_task_from_id(task.id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
task = self.workflow.get_tasks(TaskState.READY)[0]
|
task = self.workflow.get_tasks(TaskState.READY)[0]
|
||||||
self.workflow.complete_task_from_id(task.id)
|
self.workflow.run_task_from_id(task.id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
task = self.workflow.get_tasks(TaskState.READY)[0]
|
task = self.workflow.get_tasks(TaskState.READY)[0]
|
||||||
self.assertEqual('UserTaskTwo', task.get_name())
|
self.assertEqual('UserTaskTwo', task.get_name())
|
||||||
|
@ -42,6 +42,6 @@ class MultipleThrowEventStartsEventTest(BpmnWorkflowTestCase):
|
|||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||||
self.assertEqual(len(ready_tasks), 1)
|
self.assertEqual(len(ready_tasks), 1)
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.assertEqual(self.workflow.is_completed(), True)
|
self.assertEqual(self.workflow.is_completed(), True)
|
@ -62,7 +62,7 @@ class NITimerDurationTest(BpmnWorkflowTestCase):
|
|||||||
task.data['delay_reason'] = 'Just Because'
|
task.data['delay_reason'] = 'Just Because'
|
||||||
elif task.task_spec.name == 'Activity_Work':
|
elif task.task_spec.name == 'Activity_Work':
|
||||||
task.data['work_done'] = 'Yes'
|
task.data['work_done'] = 'Yes'
|
||||||
task.complete()
|
task.run()
|
||||||
self.workflow.refresh_waiting_tasks()
|
self.workflow.refresh_waiting_tasks()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.assertEqual(self.workflow.is_completed(),True)
|
self.assertEqual(self.workflow.is_completed(),True)
|
@ -55,11 +55,11 @@ class TimerCycleTest(BpmnWorkflowTestCase):
|
|||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
self.workflow.refresh_waiting_tasks()
|
self.workflow.refresh_waiting_tasks()
|
||||||
events = self.workflow.waiting_events()
|
events = self.workflow.waiting_events()
|
||||||
if loopcount == 0:
|
if loopcount < 2:
|
||||||
# Wait time is 0.1s, so the first time through, there should still be a waiting event
|
# Wait time is 0.1s, two child tasks are created
|
||||||
self.assertEqual(len(events), 1)
|
self.assertEqual(len(events), 1)
|
||||||
else:
|
else:
|
||||||
# By the second iteration, both should be complete
|
# By the third iteration, the event should no longer be waiting
|
||||||
self.assertEqual(len(events), 0)
|
self.assertEqual(len(events), 0)
|
||||||
|
|
||||||
# Get coffee still ready
|
# Get coffee still ready
|
||||||
|
@ -39,7 +39,7 @@ class TimerDurationTest(BpmnWorkflowTestCase):
|
|||||||
|
|
||||||
# Make sure the task can still be called.
|
# Make sure the task can still be called.
|
||||||
task = self.workflow.get_ready_user_tasks()[0]
|
task = self.workflow.get_ready_user_tasks()[0]
|
||||||
task.complete()
|
task.run()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.assertTrue(self.workflow.is_completed())
|
self.assertTrue(self.workflow.is_completed())
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ class TimerDurationTest(BpmnWorkflowTestCase):
|
|||||||
def actual_test(self,save_restore = False):
|
def actual_test(self,save_restore = False):
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
|
|
||||||
loopcount = 0
|
loopcount = 0
|
||||||
@ -43,7 +43,7 @@ class TimerDurationTest(BpmnWorkflowTestCase):
|
|||||||
self.assertEqual(subworkflow.state, TaskState.CANCELLED)
|
self.assertEqual(subworkflow.state, TaskState.CANCELLED)
|
||||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||||
while len(ready_tasks) > 0:
|
while len(ready_tasks) > 0:
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.assertTrue(self.workflow.is_completed())
|
self.assertTrue(self.workflow.is_completed())
|
||||||
|
@ -19,11 +19,11 @@ class TransactionSubprocessTest(BpmnWorkflowTestCase):
|
|||||||
|
|
||||||
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
||||||
ready_tasks[0].update_data({'value': 'asdf'})
|
ready_tasks[0].update_data({'value': 'asdf'})
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
||||||
ready_tasks[0].update_data({'quantity': 2})
|
ready_tasks[0].update_data({'quantity': 2})
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.assertIn('value', self.workflow.last_task.data)
|
self.assertIn('value', self.workflow.last_task.data)
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ class TransactionSubprocessTest(BpmnWorkflowTestCase):
|
|||||||
|
|
||||||
# If value == '', we cancel
|
# If value == '', we cancel
|
||||||
ready_tasks[0].update_data({'value': ''})
|
ready_tasks[0].update_data({'value': ''})
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
|
|
||||||
# If the subprocess gets cancelled, verify that data set there does not persist
|
# If the subprocess gets cancelled, verify that data set there does not persist
|
||||||
@ -72,13 +72,13 @@ class TransactionSubprocessTest(BpmnWorkflowTestCase):
|
|||||||
|
|
||||||
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
||||||
ready_tasks[0].update_data({'value': 'asdf'})
|
ready_tasks[0].update_data({'value': 'asdf'})
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
||||||
|
|
||||||
# If quantity == 0, we throw an error with no error code
|
# If quantity == 0, we throw an error with no error code
|
||||||
ready_tasks[0].update_data({'quantity': 0})
|
ready_tasks[0].update_data({'quantity': 0})
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
|
|
||||||
# We formerly checked that subprocess data does not persist, but I think it should persist
|
# We formerly checked that subprocess data does not persist, but I think it should persist
|
||||||
@ -103,13 +103,13 @@ class TransactionSubprocessTest(BpmnWorkflowTestCase):
|
|||||||
|
|
||||||
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
||||||
ready_tasks[0].update_data({'value': 'asdf'})
|
ready_tasks[0].update_data({'value': 'asdf'})
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
||||||
|
|
||||||
# If quantity < 0, we throw 'Error 1'
|
# If quantity < 0, we throw 'Error 1'
|
||||||
ready_tasks[0].update_data({'quantity': -1})
|
ready_tasks[0].update_data({'quantity': -1})
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
|
|
||||||
# The cancel boundary event should be cancelled
|
# The cancel boundary event should be cancelled
|
||||||
|
@ -142,7 +142,7 @@ class BpmnWorkflowSerializerTest(BaseTestCase):
|
|||||||
def test_serialize_workflow_where_script_task_includes_function(self):
|
def test_serialize_workflow_where_script_task_includes_function(self):
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
results = self.serializer.serialize_json(self.workflow)
|
results = self.serializer.serialize_json(self.workflow)
|
||||||
assert self.workflow.is_completed()
|
assert self.workflow.is_completed()
|
||||||
@ -161,7 +161,7 @@ class BpmnWorkflowSerializerTest(BaseTestCase):
|
|||||||
self.assertEqual(w1.data, w2.data)
|
self.assertEqual(w1.data, w2.data)
|
||||||
self.assertEqual(w1.name, w2.name)
|
self.assertEqual(w1.name, w2.name)
|
||||||
for task in w1.get_ready_user_tasks():
|
for task in w1.get_ready_user_tasks():
|
||||||
w2_task = w2.get_task(task.id)
|
w2_task = w2.get_task_from_id(task.id)
|
||||||
self.assertIsNotNone(w2_task)
|
self.assertIsNotNone(w2_task)
|
||||||
self.assertEqual(task.data, w2_task.data)
|
self.assertEqual(task.data, w2_task.data)
|
||||||
|
|
||||||
|
@ -16,11 +16,12 @@ class Version_1_0_Test(BaseTestCase):
|
|||||||
def test_convert_subprocess(self):
|
def test_convert_subprocess(self):
|
||||||
# The serialization used here comes from NestedSubprocessTest saved at line 25 with version 1.0
|
# The serialization used here comes from NestedSubprocessTest saved at line 25 with version 1.0
|
||||||
fn = os.path.join(self.DATA_DIR, 'serialization', 'v1.0.json')
|
fn = os.path.join(self.DATA_DIR, 'serialization', 'v1.0.json')
|
||||||
wf = self.serializer.deserialize_json(open(fn).read())
|
with open(fn) as fh:
|
||||||
|
wf = self.serializer.deserialize_json(fh.read())
|
||||||
# We should be able to finish the workflow from this point
|
# We should be able to finish the workflow from this point
|
||||||
ready_tasks = wf.get_tasks(TaskState.READY)
|
ready_tasks = wf.get_tasks(TaskState.READY)
|
||||||
self.assertEqual('Action3', ready_tasks[0].task_spec.description)
|
self.assertEqual('Action3', ready_tasks[0].task_spec.description)
|
||||||
ready_tasks[0].complete()
|
ready_tasks[0].run()
|
||||||
wf.do_engine_steps()
|
wf.do_engine_steps()
|
||||||
self.assertEqual(True, wf.is_completed())
|
self.assertEqual(True, wf.is_completed())
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ class Version_1_1_Test(BaseTestCase):
|
|||||||
self.assertEqual(len(task.task_spec.cond_task_specs), 2)
|
self.assertEqual(len(task.task_spec.cond_task_specs), 2)
|
||||||
ready_task = wf.get_ready_user_tasks()[0]
|
ready_task = wf.get_ready_user_tasks()[0]
|
||||||
ready_task.data['NeedClarification'] = 'Yes'
|
ready_task.data['NeedClarification'] = 'Yes'
|
||||||
ready_task.complete()
|
ready_task.run()
|
||||||
wf.do_engine_steps()
|
wf.do_engine_steps()
|
||||||
ready_task = wf.get_ready_user_tasks()[0]
|
ready_task = wf.get_ready_user_tasks()[0]
|
||||||
self.assertEqual(ready_task.task_spec.name, 'Activity_A2')
|
self.assertEqual(ready_task.task_spec.name, 'Activity_A2')
|
||||||
@ -58,4 +59,15 @@ class Version_1_1_Test(BaseTestCase):
|
|||||||
fn = os.path.join(self.DATA_DIR, 'serialization', 'v1.1-multi.json')
|
fn = os.path.join(self.DATA_DIR, 'serialization', 'v1.1-multi.json')
|
||||||
with self.assertRaises(VersionMigrationError) as ctx:
|
with self.assertRaises(VersionMigrationError) as ctx:
|
||||||
wf = self.serializer.deserialize_json(open(fn).read())
|
wf = self.serializer.deserialize_json(open(fn).read())
|
||||||
self.assertEqual(ctx.exception.message, "This workflow cannot be migrated because it contains MultiInstance Tasks")
|
self.assertEqual(ctx.exception.message, "This workflow cannot be migrated because it contains MultiInstance Tasks")
|
||||||
|
|
||||||
|
def test_remove_loop_reset(self):
|
||||||
|
fn = os.path.join(self.DATA_DIR, 'serialization', 'v1.1-loop-reset.json')
|
||||||
|
wf = self.serializer.deserialize_json(open(fn).read())
|
||||||
|
# Allow 3 seconds max to allow this test to complete (there are 20 loops with a 0.1s timer)
|
||||||
|
end = time.time() + 3
|
||||||
|
while not wf.is_completed() and time.time() < end:
|
||||||
|
wf.do_engine_steps()
|
||||||
|
wf.refresh_waiting_tasks()
|
||||||
|
self.assertTrue(wf.is_completed())
|
||||||
|
self.assertEqual(wf.last_task.data['counter'], 20)
|
||||||
|
@ -39,7 +39,7 @@ class CallActivityMessageTest(BaseTestCase):
|
|||||||
current_task = ready_tasks[0]
|
current_task = ready_tasks[0]
|
||||||
self.assertEqual(current_task.task_spec.name,step[0])
|
self.assertEqual(current_task.task_spec.name,step[0])
|
||||||
current_task.update_data(step[1])
|
current_task.update_data(step[1])
|
||||||
current_task.complete()
|
current_task.run()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.workflow.refresh_waiting_tasks()
|
self.workflow.refresh_waiting_tasks()
|
||||||
if save_restore: self.save_restore()
|
if save_restore: self.save_restore()
|
||||||
|
@ -52,7 +52,7 @@ class ClashingNameTest(BaseTestCase):
|
|||||||
firsttaskid = task.id
|
firsttaskid = task.id
|
||||||
self.assertEqual(step['taskname'], task.task_spec.name)
|
self.assertEqual(step['taskname'], task.task_spec.name)
|
||||||
task.update_data({step['formvar']: step['answer']})
|
task.update_data({step['formvar']: step['answer']})
|
||||||
self.workflow.complete_task_from_id(task.id)
|
self.workflow.run_task_from_id(task.id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
if save_restore: self.save_restore()
|
if save_restore: self.save_restore()
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ class ClashingNameTest(BaseTestCase):
|
|||||||
task = self.workflow.get_ready_user_tasks()[0]
|
task = self.workflow.get_ready_user_tasks()[0]
|
||||||
self.assertEqual(step['taskname'], task.task_spec.name)
|
self.assertEqual(step['taskname'], task.task_spec.name)
|
||||||
task.update_data({step['formvar']: step['answer']})
|
task.update_data({step['formvar']: step['answer']})
|
||||||
self.workflow.complete_task_from_id(task.id)
|
self.workflow.run_task_from_id(task.id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
if save_restore: self.save_restore()
|
if save_restore: self.save_restore()
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ class DMNCustomScriptTest(BaseTestCase):
|
|||||||
|
|
||||||
def complete_manual_task(self):
|
def complete_manual_task(self):
|
||||||
manual_task = self.workflow.get_tasks_from_spec_name('manual_task')[0]
|
manual_task = self.workflow.get_tasks_from_spec_name('manual_task')[0]
|
||||||
self.workflow.complete_task_from_id(manual_task.id)
|
self.workflow.run_task_from_id(manual_task.id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
|
|
||||||
def testDmnHappy(self):
|
def testDmnHappy(self):
|
||||||
|
@ -16,7 +16,7 @@ class DMNDictTest(BaseTestCase):
|
|||||||
self.workflow = BpmnWorkflow(self.spec)
|
self.workflow = BpmnWorkflow(self.spec)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
x = self.workflow.get_ready_user_tasks()
|
x = self.workflow.get_ready_user_tasks()
|
||||||
self.workflow.complete_task_from_id(x[0].id)
|
self.workflow.run_task_from_id(x[0].id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.assertDictEqual(self.workflow.last_task.data, self.expectedResult)
|
self.assertDictEqual(self.workflow.last_task.data, self.expectedResult)
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ class DMNDictTest(BaseTestCase):
|
|||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.save_restore()
|
self.save_restore()
|
||||||
x = self.workflow.get_ready_user_tasks()
|
x = self.workflow.get_ready_user_tasks()
|
||||||
self.workflow.complete_task_from_id(x[0].id)
|
self.workflow.run_task_from_id(x[0].id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.save_restore()
|
self.save_restore()
|
||||||
self.assertDictEqual(self.workflow.last_task.data, self.expectedResult)
|
self.assertDictEqual(self.workflow.last_task.data, self.expectedResult)
|
||||||
|
@ -41,7 +41,7 @@ class ExternalMessageBoundaryTest(BaseTestCase):
|
|||||||
self.assertEqual(True, ready_tasks[1].data['caughtinterrupt'])
|
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.description)
|
||||||
self.assertEqual(False, ready_tasks[0].data['caughtinterrupt'])
|
self.assertEqual(False, ready_tasks[0].data['caughtinterrupt'])
|
||||||
ready_tasks[1].complete()
|
ready_tasks[1].run()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
# what I think is going on here is that when we hit the reset, it is updating the
|
# what I think is going on here is that when we hit the reset, it is updating the
|
||||||
# last_task and appending the data to whatever happened there, so it would make sense that
|
# last_task and appending the data to whatever happened there, so it would make sense that
|
||||||
@ -52,7 +52,7 @@ class ExternalMessageBoundaryTest(BaseTestCase):
|
|||||||
# The user activity was cancelled and we should continue from the boundary event
|
# The user activity was cancelled and we should continue from the boundary event
|
||||||
self.assertEqual(1, len(ready_tasks),'Expected to have two ready tasks')
|
self.assertEqual(1, len(ready_tasks),'Expected to have two ready tasks')
|
||||||
event = self.workflow.get_tasks_from_spec_name('Event_19detfv')[0]
|
event = self.workflow.get_tasks_from_spec_name('Event_19detfv')[0]
|
||||||
event.complete()
|
event.run()
|
||||||
self.assertEqual('SomethingDrastic', event.data['reset_var'])
|
self.assertEqual('SomethingDrastic', event.data['reset_var'])
|
||||||
self.assertEqual(False, event.data['caughtinterrupt'])
|
self.assertEqual(False, event.data['caughtinterrupt'])
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ class BusinessRuleTaskParserTest(BaseTestCase):
|
|||||||
self.assertTrue(True, "An error was raised..")
|
self.assertTrue(True, "An error was raised..")
|
||||||
self.assertEqual("InvalidDecisionTaskId", we.task_spec.name)
|
self.assertEqual("InvalidDecisionTaskId", we.task_spec.name)
|
||||||
self.maxDiff = 1000
|
self.maxDiff = 1000
|
||||||
self.assertEquals("Error evaluating expression 'spam= 1'. Rule failed on row 1. Business Rule Task 'Invalid Decision'.", str(we))
|
self.assertEqual("Error evaluating expression 'spam= 1'. Rule failed on row 1. Business Rule Task 'Invalid Decision'.", str(we))
|
||||||
|
|
||||||
def suite():
|
def suite():
|
||||||
return unittest.TestLoader().loadTestsFromTestCase(BusinessRuleTaskParserTest)
|
return unittest.TestLoader().loadTestsFromTestCase(BusinessRuleTaskParserTest)
|
||||||
|
@ -41,7 +41,7 @@ class MessageBoundaryTest(BaseTestCase):
|
|||||||
if task.task_spec.name == step[0]:
|
if task.task_spec.name == step[0]:
|
||||||
task.update_data(step[1])
|
task.update_data(step[1])
|
||||||
|
|
||||||
self.workflow.complete_task_from_id(task.id)
|
self.workflow.run_task_from_id(task.id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
time.sleep(.01)
|
time.sleep(.01)
|
||||||
self.workflow.refresh_waiting_tasks()
|
self.workflow.refresh_waiting_tasks()
|
||||||
|
@ -23,10 +23,10 @@ class MultiInstanceDMNTest(BaseTestCase):
|
|||||||
|
|
||||||
self.save_restore()
|
self.save_restore()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.workflow.complete_next()
|
self.workflow.run_next()
|
||||||
self.save_restore()
|
self.save_restore()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.workflow.complete_next()
|
self.workflow.run_next()
|
||||||
self.save_restore()
|
self.save_restore()
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.save_restore()
|
self.save_restore()
|
||||||
|
@ -29,7 +29,7 @@ class NIMessageBoundaryTest(BaseTestCase):
|
|||||||
|
|
||||||
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
||||||
self.assertEqual(1, len(ready_tasks))
|
self.assertEqual(1, len(ready_tasks))
|
||||||
self.workflow.complete_task_from_id(ready_tasks[0].id)
|
self.workflow.run_task_from_id(ready_tasks[0].id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
|
|
||||||
# first we run through a couple of steps where we answer No to each
|
# first we run through a couple of steps where we answer No to each
|
||||||
@ -45,7 +45,7 @@ class NIMessageBoundaryTest(BaseTestCase):
|
|||||||
'We got a ready task that we did not expect - %s'%(
|
'We got a ready task that we did not expect - %s'%(
|
||||||
task.task_spec.name))
|
task.task_spec.name))
|
||||||
task.data[response[0]] = response[1]
|
task.data[response[0]] = response[1]
|
||||||
self.workflow.complete_task_from_id(task.id)
|
self.workflow.run_task_from_id(task.id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
# if we have a list of tasks - that list becomes invalid
|
# if we have a list of tasks - that list becomes invalid
|
||||||
# after we do a save restore, so I'm completing the list
|
# after we do a save restore, so I'm completing the list
|
||||||
@ -66,7 +66,7 @@ class NIMessageBoundaryTest(BaseTestCase):
|
|||||||
'We got a ready task that we did not expect - %s'%(
|
'We got a ready task that we did not expect - %s'%(
|
||||||
task.task_spec.name))
|
task.task_spec.name))
|
||||||
task.data[response[0]] = response[1]
|
task.data[response[0]] = response[1]
|
||||||
self.workflow.complete_task_from_id(task.id)
|
self.workflow.run_task_from_id(task.id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
if save_restore: self.save_restore()
|
if save_restore: self.save_restore()
|
||||||
|
|
||||||
@ -75,14 +75,14 @@ class NIMessageBoundaryTest(BaseTestCase):
|
|||||||
task = ready_tasks[0]
|
task = ready_tasks[0]
|
||||||
self.assertEqual(task.task_spec.name,'Activity_DoWork')
|
self.assertEqual(task.task_spec.name,'Activity_DoWork')
|
||||||
task.data['work_done'] = 'Yes'
|
task.data['work_done'] = 'Yes'
|
||||||
self.workflow.complete_task_from_id(task.id)
|
self.workflow.run_task_from_id(task.id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
||||||
self.assertEqual(len(ready_tasks), 1)
|
self.assertEqual(len(ready_tasks), 1)
|
||||||
task = ready_tasks[0]
|
task = ready_tasks[0]
|
||||||
self.assertEqual(task.task_spec.name, 'Activity_WorkCompleted')
|
self.assertEqual(task.task_spec.name, 'Activity_WorkCompleted')
|
||||||
task.data['work_completed'] = 'Lots of Stuff'
|
task.data['work_completed'] = 'Lots of Stuff'
|
||||||
self.workflow.complete_task_from_id(task.id)
|
self.workflow.run_task_from_id(task.id)
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.assertEqual(self.workflow.is_completed(),True)
|
self.assertEqual(self.workflow.is_completed(),True)
|
||||||
self.assertEqual(self.workflow.last_task.data,{'Event_InterruptBoundary_Response': 'Youre late!',
|
self.assertEqual(self.workflow.last_task.data,{'Event_InterruptBoundary_Response': 'Youre late!',
|
||||||
|
@ -32,7 +32,7 @@ class ParseMultiInstanceTest(BaseTestCase):
|
|||||||
self.assertEqual(len(ready_tasks), 3)
|
self.assertEqual(len(ready_tasks), 3)
|
||||||
for task in ready_tasks:
|
for task in ready_tasks:
|
||||||
task.data['output_item'] = task.data['output_item'] * 2
|
task.data['output_item'] = task.data['output_item'] * 2
|
||||||
task.complete()
|
task.run()
|
||||||
|
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.assertTrue(self.workflow.is_completed())
|
self.assertTrue(self.workflow.is_completed())
|
||||||
@ -58,7 +58,7 @@ class ParseMultiInstanceTest(BaseTestCase):
|
|||||||
self.assertEqual(len(ready_tasks), 3)
|
self.assertEqual(len(ready_tasks), 3)
|
||||||
for task in ready_tasks:
|
for task in ready_tasks:
|
||||||
task.data['output_item'] = task.data['output_item'] * 2
|
task.data['output_item'] = task.data['output_item'] * 2
|
||||||
task.complete()
|
task.run()
|
||||||
|
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.assertTrue(self.workflow.is_completed())
|
self.assertTrue(self.workflow.is_completed())
|
||||||
@ -84,7 +84,7 @@ class ParseMultiInstanceTest(BaseTestCase):
|
|||||||
self.assertEqual(len(ready_tasks), 3)
|
self.assertEqual(len(ready_tasks), 3)
|
||||||
for task in ready_tasks:
|
for task in ready_tasks:
|
||||||
task.data['input_item'] = task.data['input_item'] * 2
|
task.data['input_item'] = task.data['input_item'] * 2
|
||||||
task.complete()
|
task.run()
|
||||||
|
|
||||||
self.workflow.do_engine_steps()
|
self.workflow.do_engine_steps()
|
||||||
self.assertTrue(self.workflow.is_completed())
|
self.assertTrue(self.workflow.is_completed())
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user