merged in main and resolved conflicts w/ burnettk
This commit is contained in:
commit
e2b8f17a7e
|
@ -16,10 +16,7 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# FIXME: https://github.com/mysql/mysql-connector-python/pull/86
|
||||
# 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: "safety" }
|
||||
- { python: "3.11", os: "ubuntu-latest", session: "mypy" }
|
||||
- { python: "3.10", 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}}
|
||||
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:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
|
@ -184,9 +194,6 @@ jobs:
|
|||
steps:
|
||||
- name: Check out the repository
|
||||
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
|
||||
uses: actions/setup-python@v4.2.0
|
||||
with:
|
||||
|
@ -205,9 +212,6 @@ jobs:
|
|||
steps:
|
||||
- name: Check out the repository
|
||||
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
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
|
@ -281,7 +285,7 @@ jobs:
|
|||
# so just skip everything but main
|
||||
if: github.ref_name == 'main'
|
||||
with:
|
||||
projectBaseDir: spiffworkflow-frontend
|
||||
projectBaseDir: spiffworkflow-backend
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
pyrightconfig.json
|
||||
.idea/
|
||||
t
|
||||
*~
|
||||
.dccache
|
||||
*~
|
|
@ -32,6 +32,11 @@ pipeline {
|
|||
description: 'ID of Jenkins credential for Docker registry.',
|
||||
defaultValue: params.DOCKER_CRED_ID ?: 'MISSING'
|
||||
)
|
||||
string(
|
||||
name: 'DISCORD_WEBHOOK_CRED',
|
||||
description: 'Name of cretential with Discord webhook',
|
||||
defaultValue: params.DISCORD_WEBHOOK_CRED ?: "",
|
||||
)
|
||||
booleanParam(
|
||||
name: 'PUBLISH',
|
||||
description: 'Publish built Docker images.',
|
||||
|
@ -61,6 +66,16 @@ pipeline {
|
|||
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
|
||||
post {
|
||||
|
@ -68,3 +83,43 @@ pipeline {
|
|||
cleanup { cleanWs() }
|
||||
} // post
|
||||
} // 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):
|
||||
|
||||
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']})
|
||||
|
||||
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 ]
|
||||
if len(specs) > 0:
|
||||
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,
|
||||
create_data_objects_and_io_specs,
|
||||
check_multiinstance,
|
||||
remove_loop_reset,
|
||||
)
|
||||
|
||||
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
|
||||
are now TaskDataReferences; BpmnDataSpecifications that referred to Data Objects are
|
||||
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)
|
||||
convert_timer_expressions(new)
|
||||
add_default_condition_to_cond_task_specs(new)
|
||||
create_data_objects_and_io_specs(new)
|
||||
check_multiinstance(new)
|
||||
remove_loop_reset(new)
|
||||
new['VERSION'] = "1.2"
|
||||
return new
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from ..specs.BpmnProcessSpec import BpmnProcessSpec
|
||||
from ..specs.MultiInstanceTask import MultiInstanceTask
|
||||
from ..specs.events.IntermediateEvent import _BoundaryEventParent
|
||||
|
||||
from .helpers.spec import WorkflowSpecConverter
|
||||
|
|
|
@ -2,7 +2,6 @@ from .helpers.spec import TaskSpecConverter
|
|||
|
||||
from ...specs.StartTask import StartTask
|
||||
from ...specs.Simple import Simple
|
||||
from ...specs.LoopResetTask import LoopResetTask
|
||||
|
||||
from ..specs.BpmnProcessSpec import _EndJoin
|
||||
from ..specs.BpmnSpecMixin import _BpmnCondition
|
||||
|
@ -27,8 +26,6 @@ from ..specs.events.IntermediateEvent import (
|
|||
ReceiveTask,
|
||||
)
|
||||
|
||||
from ..workflow import BpmnWorkflow
|
||||
|
||||
|
||||
class DefaultTaskSpecConverter(TaskSpecConverter):
|
||||
|
||||
|
@ -50,23 +47,6 @@ class StartTaskConverter(DefaultTaskSpecConverter):
|
|||
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):
|
||||
def __init__(self, registry):
|
||||
super().__init__(_EndJoin, registry)
|
||||
|
@ -317,7 +297,6 @@ DEFAULT_TASK_SPEC_CONVERTER_CLASSES = [
|
|||
SimpleTaskConverter,
|
||||
StartTaskConverter,
|
||||
EndJoinConverter,
|
||||
LoopResetTaskConverter,
|
||||
NoneTaskConverter,
|
||||
UserTaskConverter,
|
||||
ManualTaskConverter,
|
||||
|
|
|
@ -246,7 +246,7 @@ class BpmnWorkflowSerializer:
|
|||
|
||||
if isinstance(task_spec, SubWorkflowTask) and task_id in top_dct.get('subprocesses', {}):
|
||||
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.data = self.data_converter.restore(subprocess_dct.pop('data'))
|
||||
subprocess.success = subprocess_dct.pop('success')
|
||||
|
@ -254,8 +254,12 @@ class BpmnWorkflowSerializer:
|
|||
subprocess.completed_event.connect(task_spec._on_subworkflow_completed, task)
|
||||
top_level_workflow.subprocesses[task.id] = subprocess
|
||||
|
||||
for child in [ process_dct['tasks'][c] for c in task_dict['children'] ]:
|
||||
self.task_tree_from_dict(process_dct, child['id'], task, process, top, top_dct)
|
||||
for child_task_id in task_dict['children']:
|
||||
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
|
||||
|
||||
|
|
|
@ -47,9 +47,10 @@ class _EndJoin(UnstructuredJoin):
|
|||
|
||||
return force or len(waiting_tasks) == 0, waiting_tasks
|
||||
|
||||
def _on_complete_hook(self, my_task):
|
||||
super(_EndJoin, self)._on_complete_hook(my_task)
|
||||
def _run_hook(self, my_task):
|
||||
result = super(_EndJoin, self)._run_hook(my_task)
|
||||
my_task.workflow.data.update(my_task.data)
|
||||
return result
|
||||
|
||||
|
||||
class BpmnProcessSpec(WorkflowSpec):
|
||||
|
|
|
@ -110,11 +110,12 @@ class InclusiveGateway(MultiChoice, UnstructuredJoin):
|
|||
|
||||
return complete, waiting_tasks
|
||||
|
||||
def _on_complete_hook(self, my_task):
|
||||
def _run_hook(self, my_task):
|
||||
outputs = self._get_matching_outputs(my_task)
|
||||
if len(outputs) == 0:
|
||||
raise WorkflowTaskException(f'No conditions satisfied on gateway', task=my_task)
|
||||
my_task._sync_children(outputs, TaskState.FUTURE)
|
||||
return True
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
|
|
|
@ -29,14 +29,14 @@ class ScriptEngineTask(Simple, BpmnSpecMixin):
|
|||
"""Please override for specific Implementations, see ScriptTask below for an example"""
|
||||
pass
|
||||
|
||||
def _on_complete_hook(self, task):
|
||||
def _run_hook(self, task):
|
||||
try:
|
||||
self._execute(task)
|
||||
super(ScriptEngineTask, self)._on_complete_hook(task)
|
||||
super(ScriptEngineTask, self)._run_hook(task)
|
||||
except Exception as exc:
|
||||
task._set_state(TaskState.WAITING)
|
||||
raise exc
|
||||
|
||||
return True
|
||||
|
||||
class ScriptTask(ScriptEngineTask):
|
||||
|
||||
|
|
|
@ -25,9 +25,6 @@ class SubWorkflowTask(BpmnSpecMixin):
|
|||
def spec_type(self):
|
||||
return 'Subprocess'
|
||||
|
||||
def _on_ready_hook(self, my_task):
|
||||
super()._on_ready_hook(my_task)
|
||||
|
||||
def _on_subworkflow_completed(self, subworkflow, my_task):
|
||||
self.update_data(my_task, subworkflow)
|
||||
my_task._set_state(TaskState.READY)
|
||||
|
|
|
@ -54,14 +54,14 @@ class UnstructuredJoin(Join, BpmnSpecMixin):
|
|||
last_changed = None
|
||||
thread_tasks = []
|
||||
for task in split_task._find_any(self):
|
||||
# Ignore tasks from other threads.
|
||||
if task.thread_id != my_task.thread_id:
|
||||
# Ignore tasks from other threads. (Do we need this condition?)
|
||||
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():
|
||||
# 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
|
||||
# We have found a matching instance.
|
||||
thread_tasks.append(task)
|
||||
|
@ -77,19 +77,12 @@ class UnstructuredJoin(Join, BpmnSpecMixin):
|
|||
for task in thread_tasks:
|
||||
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:
|
||||
if task == last_changed:
|
||||
task.data.update(collected_data)
|
||||
self.entered_event.emit(my_task.workflow, my_task)
|
||||
task._ready()
|
||||
else:
|
||||
task._set_state(TaskState.COMPLETED)
|
||||
if task != last_changed:
|
||||
task._set_state(TaskState.CANCELLED)
|
||||
task._drop_children()
|
||||
else:
|
||||
task.data.update(collected_data)
|
||||
|
||||
def task_should_set_children_future(self, my_task):
|
||||
return True
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
# 02110-1301 USA
|
||||
|
||||
from .event_types import ThrowingEvent, CatchingEvent
|
||||
from .event_definitions import CycleTimerEventDefinition
|
||||
from ..BpmnSpecMixin import BpmnSpecMixin
|
||||
from ....specs.Simple import Simple
|
||||
from ....task import TaskState
|
||||
|
@ -67,13 +66,15 @@ class _BoundaryEventParent(Simple, BpmnSpecMixin):
|
|||
def spec_type(self):
|
||||
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
|
||||
# wait for new events
|
||||
for child in my_task.children:
|
||||
if isinstance(child.task_spec, BoundaryEvent):
|
||||
child.task_spec.event_definition.reset(child)
|
||||
child._set_state(TaskState.WAITING)
|
||||
return True
|
||||
|
||||
def _child_complete_hook(self, child_task):
|
||||
|
||||
|
@ -123,7 +124,7 @@ class BoundaryEvent(CatchingEvent):
|
|||
super(BoundaryEvent, self).catch(my_task, event_definition)
|
||||
# Would love to get rid of this statement and manage in the workflow
|
||||
# However, it is not really compatible with how boundary events work.
|
||||
my_task.complete()
|
||||
my_task.run()
|
||||
|
||||
|
||||
class EventBasedGateway(CatchingEvent):
|
||||
|
@ -135,8 +136,8 @@ class EventBasedGateway(CatchingEvent):
|
|||
def _predict_hook(self, my_task):
|
||||
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:
|
||||
if not child.task_spec.event_definition.has_fired(child):
|
||||
if child.task_spec.event_definition not in seen_events:
|
||||
child.cancel()
|
||||
return super()._on_complete_hook(my_task)
|
|
@ -20,13 +20,14 @@
|
|||
import re
|
||||
from datetime import datetime, timedelta, timezone
|
||||
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 SpiffWorkflow.exceptions import SpiffWorkflowException, WorkflowException
|
||||
from SpiffWorkflow.exceptions import WorkflowException
|
||||
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):
|
||||
|
@ -452,42 +453,38 @@ class CycleTimerEventDefinition(TimerEventDefinition):
|
|||
def event_type(self):
|
||||
return 'Cycle Timer'
|
||||
|
||||
def has_fired(self, my_task):
|
||||
def cycle_complete(self, my_task):
|
||||
|
||||
if not my_task._get_internal_data('event_fired'):
|
||||
# Only check for the next cycle when the event has not fired to prevent cycles from being skipped.
|
||||
event_value = my_task._get_internal_data('event_value')
|
||||
if event_value is None:
|
||||
expression = my_task.workflow.script_engine.evaluate(my_task, self.expression)
|
||||
cycles, start, duration = TimerEventDefinition.parse_iso_recurring_interval(expression)
|
||||
event_value = {'cycles': cycles, 'next': start.isoformat(), 'duration': duration.total_seconds()}
|
||||
event_value = my_task._get_internal_data('event_value')
|
||||
if event_value is None:
|
||||
# Don't necessarily like this, but it's a lot more staightforward than trying to only create
|
||||
# a child task on loop iterations after the first
|
||||
my_task._drop_children()
|
||||
expression = my_task.workflow.script_engine.evaluate(my_task, self.expression)
|
||||
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:
|
||||
next_event = datetime.fromisoformat(event_value['next'])
|
||||
if next_event < datetime.now(timezone.utc):
|
||||
my_task._set_internal_data(event_fired=True)
|
||||
event_value['next'] = (next_event + timedelta(seconds=event_value['duration'])).isoformat()
|
||||
# When the next timer event passes, return True to allow the parent task to generate another child
|
||||
# Use event fired to indicate that this timer has completed all cycles and the task can be completed
|
||||
ready = False
|
||||
if event_value['cycles'] != 0:
|
||||
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)
|
||||
|
||||
return my_task._get_internal_data('event_fired', False)
|
||||
my_task._set_internal_data(event_value=event_value)
|
||||
return ready
|
||||
|
||||
def timer_value(self, my_task):
|
||||
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']
|
||||
|
||||
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):
|
||||
|
||||
|
@ -504,11 +501,10 @@ class MultipleEventDefinition(EventDefinition):
|
|||
|
||||
seen_events = my_task.internal_data.get('seen_events', [])
|
||||
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[0].task_spec._update_hook(child[0])
|
||||
child[0]._set_state(TaskState.MAYBE)
|
||||
if event.has_fired(my_task):
|
||||
if event.has_fired(child[0]):
|
||||
seen_events.append(event)
|
||||
|
||||
if self.parallel:
|
||||
|
|
|
@ -57,21 +57,22 @@ class CatchingEvent(Simple, BpmnSpecMixin):
|
|||
|
||||
if self.event_definition.has_fired(my_task):
|
||||
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)
|
||||
|
||||
def _on_complete_hook(self, my_task):
|
||||
def _run_hook(self, my_task):
|
||||
|
||||
if isinstance(self.event_definition, MessageEventDefinition):
|
||||
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)
|
||||
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.
|
||||
# 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)
|
||||
self.event_definition = event_definition
|
||||
|
||||
def _on_complete_hook(self, my_task):
|
||||
super(ThrowingEvent, self)._on_complete_hook(my_task)
|
||||
def _run_hook(self, my_task):
|
||||
super(ThrowingEvent, self)._run_hook(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 ..task import TaskState, Task
|
||||
from ..workflow import Workflow
|
||||
from ..exceptions import WorkflowException, WorkflowTaskException
|
||||
from ..exceptions import TaskNotFoundException, WorkflowException, WorkflowTaskException
|
||||
|
||||
|
||||
class BpmnMessage:
|
||||
|
@ -251,7 +251,7 @@ class BpmnWorkflow(Workflow):
|
|||
for task in engine_steps:
|
||||
if will_complete_task is not None:
|
||||
will_complete_task(task)
|
||||
task.complete()
|
||||
task.run()
|
||||
if did_complete_task is not None:
|
||||
did_complete_task(task)
|
||||
if task.task_spec.name == exit_at:
|
||||
|
@ -271,7 +271,10 @@ class BpmnWorkflow(Workflow):
|
|||
for my_task in self.get_tasks(TaskState.WAITING):
|
||||
if will_refresh_task is not None:
|
||||
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:
|
||||
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]
|
||||
|
||||
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 = []
|
||||
top = self._get_outermost_workflow()
|
||||
wf = workflow or top
|
||||
for task in Workflow.get_tasks(wf):
|
||||
# I think it makes more sense to start with the current workflow, which is probably going to be the top
|
||||
# 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)
|
||||
if subprocess is not None:
|
||||
tasks.extend(subprocess.get_tasks(state, subprocess))
|
||||
|
@ -290,42 +298,28 @@ class BpmnWorkflow(Workflow):
|
|||
tasks.append(task)
|
||||
return tasks
|
||||
|
||||
def _find_task(self, task_id):
|
||||
if task_id is None:
|
||||
raise WorkflowException('task_id is None', task_spec=self.spec)
|
||||
for task in self.get_tasks():
|
||||
def get_task_from_id(self, task_id, workflow=None):
|
||||
for task in self.get_tasks(workflow=workflow):
|
||||
if task.id == task_id:
|
||||
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):
|
||||
# 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):
|
||||
def get_ready_user_tasks(self, lane=None, workflow=None):
|
||||
"""Returns a list of User Tasks that are READY for user action"""
|
||||
if lane is not None:
|
||||
return [t for t in self.get_tasks(TaskState.READY)
|
||||
return [t for t in self.get_tasks(TaskState.READY, workflow)
|
||||
if (not self._is_engine_task(t.task_spec))
|
||||
and (t.task_spec.lane == lane)]
|
||||
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)]
|
||||
|
||||
def get_waiting_tasks(self):
|
||||
def get_waiting_tasks(self, workflow=None):
|
||||
"""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):
|
||||
return [ task for task in self.get_tasks() if isinstance(task.task_spec, CatchingEvent) ]
|
||||
def get_catching_tasks(self, workflow=None):
|
||||
return [task for task in self.get_tasks(workflow=workflow) if isinstance(task.task_spec, CatchingEvent)]
|
||||
|
||||
def _is_engine_task(self, task_spec):
|
||||
return (not hasattr(task_spec, 'is_engine_task') or task_spec.is_engine_task())
|
||||
|
|
|
@ -5,10 +5,7 @@ from ..specs.model import DecisionTable, Rule, HitPolicy
|
|||
from ..specs.model import Input, InputEntry, Output, OutputEntry
|
||||
from ..engine.DMNEngine import DMNEngine
|
||||
|
||||
class BusinessRuleTaskConverter(TaskSpecConverter):
|
||||
|
||||
def __init__(self, registry):
|
||||
super().__init__(BusinessRuleTask, registry)
|
||||
class BaseBusinessRuleTaskConverter(TaskSpecConverter):
|
||||
|
||||
def to_dict(self, spec):
|
||||
dct = self.get_default_attributes(spec)
|
||||
|
@ -98,3 +95,8 @@ class BusinessRuleTaskConverter(TaskSpecConverter):
|
|||
rule.outputEntries = [self.output_entry_from_dict(entry, outputs)
|
||||
for entry in dct['output_entries']]
|
||||
return rule
|
||||
|
||||
|
||||
class BusinessRuleTaskConverter(BaseBusinessRuleTaskConverter):
|
||||
def __init__(self, registry):
|
||||
super().__init__(BusinessRuleTask, registry)
|
|
@ -1,5 +1,4 @@
|
|||
from SpiffWorkflow.exceptions import WorkflowTaskException, WorkflowException, \
|
||||
SpiffWorkflowException
|
||||
from SpiffWorkflow.exceptions import WorkflowTaskException, SpiffWorkflowException
|
||||
|
||||
from ...specs.Simple import Simple
|
||||
|
||||
|
@ -17,7 +16,6 @@ class BusinessRuleTask(Simple, BpmnSpecMixin):
|
|||
|
||||
def __init__(self, wf_spec, name, dmnEngine, **kwargs):
|
||||
super().__init__(wf_spec, name, **kwargs)
|
||||
|
||||
self.dmnEngine = dmnEngine
|
||||
self.resDict = None
|
||||
|
||||
|
@ -25,11 +23,10 @@ class BusinessRuleTask(Simple, BpmnSpecMixin):
|
|||
def spec_class(self):
|
||||
return 'Business Rule Task'
|
||||
|
||||
def _on_complete_hook(self, my_task):
|
||||
def _run_hook(self, my_task):
|
||||
try:
|
||||
my_task.data = DeepMerge.merge(my_task.data,
|
||||
self.dmnEngine.result(my_task))
|
||||
super(BusinessRuleTask, self)._on_complete_hook(my_task)
|
||||
my_task.data = DeepMerge.merge(my_task.data, self.dmnEngine.result(my_task))
|
||||
super(BusinessRuleTask, self)._run_hook(my_task)
|
||||
except SpiffWorkflowException as we:
|
||||
we.add_note(f"Business Rule Task '{my_task.task_spec.description}'.")
|
||||
raise we
|
||||
|
@ -37,4 +34,4 @@ class BusinessRuleTask(Simple, BpmnSpecMixin):
|
|||
error = WorkflowTaskException(str(e), task=my_task)
|
||||
error.add_note(f"Business Rule Task '{my_task.task_spec.description}'.")
|
||||
raise error
|
||||
|
||||
return True
|
||||
|
|
|
@ -129,3 +129,7 @@ class WorkflowTaskException(WorkflowException):
|
|||
|
||||
class StorageException(SpiffWorkflowException):
|
||||
pass
|
||||
|
||||
|
||||
class TaskNotFoundException(WorkflowException):
|
||||
pass
|
||||
|
|
|
@ -16,9 +16,7 @@ from builtins import object
|
|||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
|
||||
import re
|
||||
from .. import operators
|
||||
from .. import specs
|
||||
from ..specs.AcquireMutex import AcquireMutex
|
||||
from ..specs.Cancel import Cancel
|
||||
from ..specs.CancelTask import CancelTask
|
||||
|
@ -41,7 +39,6 @@ from ..specs.ThreadSplit import ThreadSplit
|
|||
from ..specs.Transform import Transform
|
||||
from ..specs.Trigger import Trigger
|
||||
from ..specs.WorkflowSpec import WorkflowSpec
|
||||
from ..specs.LoopResetTask import LoopResetTask
|
||||
|
||||
# Create a list of tag names out of the spec names.
|
||||
def spec_map():
|
||||
|
@ -68,7 +65,6 @@ def spec_map():
|
|||
'transform': Transform,
|
||||
'trigger': Trigger,
|
||||
'workflow-spec': WorkflowSpec,
|
||||
'loop-reset-task': LoopResetTask,
|
||||
'task': Simple,
|
||||
}
|
||||
|
||||
|
|
|
@ -22,8 +22,7 @@ from base64 import b64encode, b64decode
|
|||
from ..workflow import Workflow
|
||||
from ..util.impl import get_class
|
||||
from ..task import Task
|
||||
from ..operators import (Attrib, PathAttrib, Equal, NotEqual,
|
||||
Operator, GreaterThan, LessThan, Match)
|
||||
from ..operators import (Attrib, PathAttrib, Equal, NotEqual, Operator, GreaterThan, LessThan, Match)
|
||||
from ..specs.base import TaskSpec
|
||||
from ..specs.AcquireMutex import AcquireMutex
|
||||
from ..specs.Cancel import Cancel
|
||||
|
@ -44,10 +43,8 @@ from ..specs.SubWorkflow import SubWorkflow
|
|||
from ..specs.ThreadStart import ThreadStart
|
||||
from ..specs.ThreadMerge import ThreadMerge
|
||||
from ..specs.ThreadSplit import ThreadSplit
|
||||
from ..specs.Transform import Transform
|
||||
from ..specs.Trigger import Trigger
|
||||
from ..specs.WorkflowSpec import WorkflowSpec
|
||||
from ..specs.LoopResetTask import LoopResetTask
|
||||
from .base import Serializer
|
||||
from .exceptions import TaskNotSupportedError, MissingSpecError
|
||||
import warnings
|
||||
|
@ -169,7 +166,6 @@ class DictionarySerializer(Serializer):
|
|||
s_state['defines'] = self.serialize_dict(spec.defines)
|
||||
s_state['pre_assign'] = self.serialize_list(spec.pre_assign)
|
||||
s_state['post_assign'] = self.serialize_list(spec.post_assign)
|
||||
s_state['locks'] = spec.locks[:]
|
||||
|
||||
# Note: Events are not serialized; this is documented in
|
||||
# the TaskSpec API docs.
|
||||
|
@ -190,7 +186,6 @@ class DictionarySerializer(Serializer):
|
|||
spec.pre_assign = self.deserialize_list(s_state.get('pre_assign', []))
|
||||
spec.post_assign = self.deserialize_list(
|
||||
s_state.get('post_assign', []))
|
||||
spec.locks = s_state.get('locks', [])[:]
|
||||
# We can't restore inputs and outputs yet because they may not be
|
||||
# deserialized yet. So keep the names, and resolve them in the end.
|
||||
spec.inputs = s_state.get('inputs', [])[:]
|
||||
|
@ -302,18 +297,6 @@ class DictionarySerializer(Serializer):
|
|||
self.deserialize_task_spec(wf_spec, s_state, spec=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):
|
||||
s_state = self.serialize_task_spec(spec)
|
||||
s_state['split_task'] = spec.split_task
|
||||
|
@ -555,8 +538,7 @@ class DictionarySerializer(Serializer):
|
|||
|
||||
del spec.task_specs['Start']
|
||||
start_task_spec_state = s_state['task_specs']['Start']
|
||||
start_task_spec = StartTask.deserialize(
|
||||
self, spec, start_task_spec_state)
|
||||
start_task_spec = StartTask.deserialize(self, spec, start_task_spec_state)
|
||||
spec.start = start_task_spec
|
||||
spec.task_specs['Start'] = start_task_spec
|
||||
for name, task_spec_state in list(s_state['task_specs'].items()):
|
||||
|
@ -602,20 +584,34 @@ class DictionarySerializer(Serializer):
|
|||
s_state['wf_spec']"""
|
||||
|
||||
if wf_spec is None:
|
||||
# The json serializer serializes the spec as a string and then serializes it again, hence this check
|
||||
# I'm not confident that this is going to actually work, but this serializer is so fundamentally flawed
|
||||
# that I'm not going to put the effort in to be sure this works.
|
||||
if isinstance(s_state['wf_spec'], str):
|
||||
spec_dct = json.loads(s_state['wf_spec'])
|
||||
else:
|
||||
spec_dct = s_state['wf_spec']
|
||||
reset_specs = [spec['name'] for spec in spec_dct['task_specs'].values() if spec['class'].endswith('LoopResetTask')]
|
||||
for name in reset_specs:
|
||||
s_state['wf_spec']['task_specs'].pop(name)
|
||||
wf_spec = self.deserialize_workflow_spec(s_state['wf_spec'], **kwargs)
|
||||
else:
|
||||
reset_specs = []
|
||||
|
||||
workflow = wf_class(wf_spec)
|
||||
workflow.data = self.deserialize_dict(s_state['data'])
|
||||
workflow.success = s_state['success']
|
||||
workflow.spec = wf_spec
|
||||
workflow.task_tree = self.deserialize_task(
|
||||
workflow, s_state['task_tree'])
|
||||
workflow.task_tree = self.deserialize_task(workflow, s_state['task_tree'], reset_specs)
|
||||
|
||||
# Re-connect parents
|
||||
tasklist = list(workflow.get_tasks())
|
||||
tasklist = workflow.get_tasks()
|
||||
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()
|
||||
|
||||
return workflow
|
||||
|
@ -636,81 +632,48 @@ class DictionarySerializer(Serializer):
|
|||
" internal_data to store the subworkflow).")
|
||||
|
||||
s_state = dict()
|
||||
|
||||
# id
|
||||
s_state['id'] = task.id
|
||||
|
||||
# workflow
|
||||
s_state['workflow_name'] = task.workflow.name
|
||||
|
||||
# parent
|
||||
s_state['parent'] = task.parent.id if task.parent is not None else None
|
||||
|
||||
# children
|
||||
if not skip_children:
|
||||
s_state['children'] = [
|
||||
self.serialize_task(child) for child in task.children]
|
||||
|
||||
# state
|
||||
s_state['state'] = task.state
|
||||
s_state['triggered'] = task.triggered
|
||||
|
||||
# task_spec
|
||||
s_state['task_spec'] = task.task_spec.name
|
||||
|
||||
# last_state_change
|
||||
s_state['last_state_change'] = task.last_state_change
|
||||
|
||||
# data
|
||||
s_state['data'] = self.serialize_dict(task.data)
|
||||
|
||||
# internal_data
|
||||
s_state['internal_data'] = task.internal_data
|
||||
|
||||
return s_state
|
||||
|
||||
|
||||
def deserialize_task(self, workflow, s_state):
|
||||
def deserialize_task(self, workflow, s_state, ignored_specs=None):
|
||||
assert isinstance(workflow, Workflow)
|
||||
splits = s_state['task_spec'].split('_')
|
||||
oldtaskname = s_state['task_spec']
|
||||
task_spec = workflow.get_task_spec_from_name(oldtaskname)
|
||||
old_spec_name = s_state['task_spec']
|
||||
if old_spec_name in ignored_specs:
|
||||
return None
|
||||
task_spec = workflow.get_task_spec_from_name(old_spec_name)
|
||||
if task_spec is None:
|
||||
raise MissingSpecError("Unknown task spec: " + oldtaskname)
|
||||
raise MissingSpecError("Unknown task spec: " + old_spec_name)
|
||||
task = Task(workflow, task_spec)
|
||||
|
||||
if getattr(task_spec,'isSequential',False) and \
|
||||
s_state['internal_data'].get('splits') is not None:
|
||||
if getattr(task_spec,'isSequential',False) and s_state['internal_data'].get('splits') is not None:
|
||||
task.task_spec.expanded = s_state['internal_data']['splits']
|
||||
|
||||
|
||||
# id
|
||||
task.id = s_state['id']
|
||||
|
||||
# parent
|
||||
# as the task_tree might not be complete yet
|
||||
# keep the ids so they can be processed at the end
|
||||
task.parent = s_state['parent']
|
||||
|
||||
# children
|
||||
task.children = self._deserialize_task_children(task, s_state)
|
||||
|
||||
# state
|
||||
task.children = self._deserialize_task_children(task, s_state, ignored_specs)
|
||||
task._state = s_state['state']
|
||||
task.triggered = s_state['triggered']
|
||||
|
||||
# last_state_change
|
||||
task.last_state_change = s_state['last_state_change']
|
||||
|
||||
# data
|
||||
task.data = self.deserialize_dict(s_state['data'])
|
||||
|
||||
# internal_data
|
||||
task.internal_data = s_state['internal_data']
|
||||
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
|
||||
deserialization of sub-workflows"""
|
||||
return [self.deserialize_task(task.workflow, c)
|
||||
for c in s_state['children']]
|
||||
children = [self.deserialize_task(task.workflow, c, ignored_specs) 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):
|
||||
|
||||
def serialize_workflow_spec(self, wf_spec, **kwargs):
|
||||
thedict = super(JSONSerializer, self).serialize_workflow_spec(
|
||||
wf_spec, **kwargs)
|
||||
thedict = super(JSONSerializer, self).serialize_workflow_spec(wf_spec, **kwargs)
|
||||
return self._dumps(thedict)
|
||||
|
||||
def deserialize_workflow_spec(self, s_state, **kwargs):
|
||||
thedict = self._loads(s_state)
|
||||
return super(JSONSerializer, self).deserialize_workflow_spec(
|
||||
thedict, **kwargs)
|
||||
return super(JSONSerializer, self).deserialize_workflow_spec(thedict, **kwargs)
|
||||
|
||||
def serialize_workflow(self, workflow, **kwargs):
|
||||
thedict = super(JSONSerializer, self).serialize_workflow(
|
||||
workflow, **kwargs)
|
||||
thedict = super(JSONSerializer, self).serialize_workflow(workflow, **kwargs)
|
||||
return self._dumps(thedict)
|
||||
|
||||
def deserialize_workflow(self, s_state, **kwargs):
|
||||
thedict = self._loads(s_state)
|
||||
return super(JSONSerializer, self).deserialize_workflow(
|
||||
thedict, **kwargs)
|
||||
return super(JSONSerializer, self).deserialize_workflow(thedict, **kwargs)
|
||||
|
||||
def _object_hook(self, dct):
|
||||
if '__uuid__' in dct:
|
||||
|
|
|
@ -176,8 +176,7 @@ class XmlSerializer(Serializer):
|
|||
threshold_field = start_node.attrib.get('threshold-field', '').lower()
|
||||
file_name = start_node.attrib.get('file', '').lower()
|
||||
file_field = start_node.attrib.get('file-field', '').lower()
|
||||
kwargs = {'lock': [],
|
||||
'data': {},
|
||||
kwargs = {'data': {},
|
||||
'defines': {},
|
||||
'pre_assign': [],
|
||||
'post_assign': []}
|
||||
|
@ -253,10 +252,6 @@ class XmlSerializer(Serializer):
|
|||
elif not isinstance(context, list):
|
||||
context = [context]
|
||||
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':
|
||||
if not node.text:
|
||||
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
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
import re
|
||||
import warnings
|
||||
from lxml import etree
|
||||
from lxml.etree import SubElement
|
||||
from ..workflow import Workflow
|
||||
from .. import specs, operators
|
||||
from ..task import Task, TaskStateNames
|
||||
from ..operators import (Attrib, Assign, PathAttrib, Equal, NotEqual,
|
||||
GreaterThan, LessThan, Match)
|
||||
from ..operators import (Attrib, Assign, PathAttrib, Equal, NotEqual, GreaterThan, LessThan, Match)
|
||||
from ..specs.AcquireMutex import AcquireMutex
|
||||
from ..specs.Cancel import Cancel
|
||||
from ..specs.CancelTask import CancelTask
|
||||
|
@ -43,10 +40,8 @@ from ..specs.SubWorkflow import SubWorkflow
|
|||
from ..specs.ThreadStart import ThreadStart
|
||||
from ..specs.ThreadMerge import ThreadMerge
|
||||
from ..specs.ThreadSplit import ThreadSplit
|
||||
from ..specs.Transform import Transform
|
||||
from ..specs.Trigger import Trigger
|
||||
from ..specs.WorkflowSpec import WorkflowSpec
|
||||
from ..specs.LoopResetTask import LoopResetTask
|
||||
from .base import Serializer, spec_map, op_map
|
||||
from .exceptions import TaskNotSupportedError
|
||||
|
||||
|
@ -726,31 +721,17 @@ class XmlSerializer(Serializer):
|
|||
workflow.task_tree = self.deserialize_task(workflow, task_tree_elem[0])
|
||||
|
||||
# Re-connect parents
|
||||
for task in workflow.get_tasks():
|
||||
task.parent = workflow.get_task(task.parent)
|
||||
for task in workflow.get_tasks_iterator():
|
||||
if task.parent is not None:
|
||||
task.parent = workflow.get_task_from_id(task.parent)
|
||||
|
||||
# last_task
|
||||
last_task = elem.findtext('last-task')
|
||||
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
|
||||
|
||||
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):
|
||||
assert isinstance(task, Task)
|
||||
|
||||
|
|
|
@ -55,9 +55,9 @@ class Cancel(TaskSpec):
|
|||
if len(self.outputs) > 0:
|
||||
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)
|
||||
TaskSpec._on_complete_hook(self, my_task)
|
||||
return True
|
||||
|
||||
def serialize(self, serializer):
|
||||
return serializer.serialize_cancel(self)
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
from .base import TaskSpec
|
||||
from .Trigger import Trigger
|
||||
|
||||
|
||||
|
@ -30,12 +29,12 @@ class CancelTask(Trigger):
|
|||
parallel split.
|
||||
"""
|
||||
|
||||
def _on_complete_hook(self, my_task):
|
||||
def _run_hook(self, my_task):
|
||||
for task_name in self.context:
|
||||
cancel_tasks = my_task.workflow.get_task_spec_from_name(task_name)
|
||||
for cancel_task in my_task._get_root()._find_any(cancel_tasks):
|
||||
cancel_task.cancel()
|
||||
TaskSpec._on_complete_hook(self, my_task)
|
||||
return True
|
||||
|
||||
def serialize(self, serializer):
|
||||
return serializer.serialize_cancel_task(self)
|
||||
|
|
|
@ -117,7 +117,7 @@ class Celery(TaskSpec):
|
|||
self.call = call or []
|
||||
self.args = call_args or {}
|
||||
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.result_key = result_key
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ class Choose(Trigger):
|
|||
self.context = context
|
||||
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)
|
||||
triggered = []
|
||||
for task in my_task.workflow.task_tree:
|
||||
|
@ -66,7 +66,7 @@ class Choose(Trigger):
|
|||
triggered.append(task)
|
||||
for task in triggered:
|
||||
context._predict(task)
|
||||
TaskSpec._on_complete_hook(self, my_task)
|
||||
return True
|
||||
|
||||
def serialize(self, serializer):
|
||||
return serializer.serialize_choose(self)
|
||||
|
|
|
@ -61,16 +61,7 @@ class ExclusiveChoice(MultiChoice):
|
|||
if self.default_task_spec is None:
|
||||
raise WorkflowException('A default output is required.', task_spec=self)
|
||||
|
||||
def _predict_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):
|
||||
def _run_hook(self, my_task):
|
||||
|
||||
output = self._wf_spec.get_task_spec_from_name(self.default_task_spec)
|
||||
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)
|
||||
|
||||
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):
|
||||
return serializer.serialize_exclusive_choice(self)
|
||||
|
|
|
@ -120,8 +120,7 @@ class Join(TaskSpec):
|
|||
# If the task is predicted with less outputs than he has
|
||||
# children, that means the prediction may be incomplete (for
|
||||
# example, because a prediction is not yet possible at this time).
|
||||
if not child._is_definite() \
|
||||
and len(child.task_spec.outputs) > len(child.children):
|
||||
if child._is_predicted() and len(child.task_spec.outputs) > len(child.children):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -153,8 +152,8 @@ class Join(TaskSpec):
|
|||
|
||||
# Look at the tree to find all places where this task is used.
|
||||
tasks = []
|
||||
for input in self.inputs:
|
||||
tasks += my_task.workflow.task_mapping[my_task.thread_id][input]
|
||||
for spec in self.inputs:
|
||||
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.
|
||||
waiting_tasks = []
|
||||
|
@ -162,7 +161,7 @@ class Join(TaskSpec):
|
|||
for task in tasks:
|
||||
if task.parent is None or task._has_state(TaskState.COMPLETED):
|
||||
completed += 1
|
||||
else:
|
||||
elif not task._is_finished():
|
||||
waiting_tasks.append(task)
|
||||
|
||||
# If the threshold was reached, get ready to fire.
|
||||
|
@ -186,8 +185,6 @@ class Join(TaskSpec):
|
|||
waiting_tasks = []
|
||||
completed = 0
|
||||
for task in tasks:
|
||||
# Refresh path prediction.
|
||||
task.task_spec._predict(task)
|
||||
if not self._branch_may_merge_at(task):
|
||||
completed += 1
|
||||
elif self._branch_is_complete(task):
|
||||
|
@ -204,16 +201,16 @@ class Join(TaskSpec):
|
|||
Returns True if the threshold was reached, False otherwise.
|
||||
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._has_state(TaskState.COMPLETED):
|
||||
return True, None
|
||||
if my_task._is_finished():
|
||||
return False, None
|
||||
if my_task._has_state(TaskState.READY):
|
||||
return True, None
|
||||
|
||||
# Check whether we may fire.
|
||||
if self.split_task is None:
|
||||
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):
|
||||
# Check whether enough incoming branches have completed.
|
||||
|
@ -224,22 +221,16 @@ class Join(TaskSpec):
|
|||
if self.cancel_remaining:
|
||||
for task in waiting_tasks:
|
||||
task.cancel()
|
||||
|
||||
# Update the state of our child objects.
|
||||
self._do_join(my_task)
|
||||
else:
|
||||
return True
|
||||
elif not my_task._is_finished():
|
||||
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)
|
||||
|
||||
# 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 = []
|
||||
for task in split_task._find_any(self):
|
||||
# Ignore tasks from other threads.
|
||||
|
@ -248,27 +239,16 @@ class Join(TaskSpec):
|
|||
# Ignore my outgoing branches.
|
||||
if self.split_task and task._is_descendant_of(my_task):
|
||||
continue
|
||||
|
||||
# We have found a matching instance.
|
||||
thread_tasks.append(task)
|
||||
return thread_tasks
|
||||
|
||||
# Check whether the state of the instance was recently
|
||||
# changed.
|
||||
changed = task.parent.last_state_change
|
||||
if last_changed is None or changed > last_changed.parent.last_state_change:
|
||||
last_changed = task
|
||||
def _do_join(self, my_task):
|
||||
|
||||
# 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:
|
||||
if task == last_changed:
|
||||
self.entered_event.emit(my_task.workflow, my_task)
|
||||
task._ready()
|
||||
else:
|
||||
task._set_state(TaskState.COMPLETED)
|
||||
# Execution will continue from this task; mark others as cancelled
|
||||
for task in self._find_tasks(my_task):
|
||||
if task != my_task:
|
||||
task._set_state(TaskState.CANCELLED)
|
||||
task._drop_children()
|
||||
|
||||
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
|
||||
completed.
|
||||
"""
|
||||
for task in my_task.workflow.task_tree._find_any(self):
|
||||
if task.thread_id != my_task.thread_id:
|
||||
continue
|
||||
self._do_join(task)
|
||||
tasks = sorted(self._find_tasks(my_task), key=lambda t: t.last_state_change)
|
||||
for task in tasks[:-1]:
|
||||
task._set_state(TaskState.CANCELLED)
|
||||
task._drop_children()
|
||||
tasks[-1]._ready()
|
||||
|
||||
def serialize(self, serializer):
|
||||
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.
|
||||
|
||||
def _predict_hook(self, my_task):
|
||||
if self.choice:
|
||||
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 = []
|
||||
conditional, unconditional = [], []
|
||||
for condition, output in self.cond_task_specs:
|
||||
if condition is None:
|
||||
outputs.append(self._wf_spec.get_task_spec_from_name(output))
|
||||
|
||||
for child in my_task.children:
|
||||
if child._is_definite():
|
||||
if self.choice is not None and output not in self.choice:
|
||||
continue
|
||||
if child.task_spec in outputs:
|
||||
child._set_state(best_state)
|
||||
if condition is None:
|
||||
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):
|
||||
outputs = []
|
||||
|
@ -125,12 +111,12 @@ class MultiChoice(TaskSpec):
|
|||
outputs.append(self._wf_spec.get_task_spec_from_name(output))
|
||||
return outputs
|
||||
|
||||
def _on_complete_hook(self, my_task):
|
||||
"""
|
||||
Runs the task. Should not be called directly.
|
||||
Returns True if completed, False otherwise.
|
||||
"""
|
||||
def _run_hook(self, my_task):
|
||||
"""Runs the task. Should not be called directly."""
|
||||
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):
|
||||
return serializer.serialize_multi_choice(self)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from builtins import range
|
||||
# Copyright (C) 2007 Samuel Abels
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
|
@ -75,33 +74,24 @@ class MultiInstance(TaskSpec):
|
|||
for output in self.outputs:
|
||||
new_task = my_task._add_child(output, state)
|
||||
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):
|
||||
split_n = int(valueof(my_task, self.times, 1))
|
||||
|
||||
# Predict the outputs.
|
||||
outputs = []
|
||||
for i in range(split_n):
|
||||
outputs += self.outputs
|
||||
return outputs
|
||||
return self.outputs * split_n
|
||||
|
||||
def _predict_hook(self, my_task):
|
||||
split_n = int(valueof(my_task, self.times, 1))
|
||||
my_task._set_internal_data(splits=split_n)
|
||||
|
||||
# Create the outgoing tasks.
|
||||
outputs = []
|
||||
for i in range(split_n):
|
||||
outputs += self.outputs
|
||||
outputs = self._get_predicted_outputs(my_task)
|
||||
if my_task._is_definite():
|
||||
my_task._sync_children(outputs, TaskState.FUTURE)
|
||||
else:
|
||||
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)
|
||||
my_task._sync_children(outputs, TaskState.FUTURE)
|
||||
self._predict(my_task, mask=TaskState.FUTURE|TaskState.PREDICTED_MASK)
|
||||
return True
|
||||
|
||||
def serialize(self, serializer):
|
||||
return serializer.serialize_multi_instance(self)
|
||||
|
|
|
@ -47,10 +47,10 @@ class ReleaseMutex(TaskSpec):
|
|||
TaskSpec.__init__(self, wf_spec, name, **kwargs)
|
||||
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.unlock()
|
||||
TaskSpec._on_complete_hook(self, my_task)
|
||||
return True
|
||||
|
||||
def serialize(self, serializer):
|
||||
return serializer.serialize_release_mutex(self)
|
||||
|
|
|
@ -98,41 +98,31 @@ class SubWorkflow(TaskSpec):
|
|||
xml = etree.parse(fp).getroot()
|
||||
wf_spec = WorkflowSpec.deserialize(serializer, xml, filename=file_name)
|
||||
outer_workflow = my_task.workflow.outer_workflow
|
||||
return Workflow(wf_spec, parent=outer_workflow)
|
||||
|
||||
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)
|
||||
subworkflow = Workflow(wf_spec, parent=outer_workflow)
|
||||
my_task._sync_children(self.outputs, TaskState.FUTURE)
|
||||
for child in subworkflow.task_tree.children:
|
||||
my_task.children.insert(0, child)
|
||||
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.
|
||||
subworkflow = my_task._get_internal_data('subworkflow')
|
||||
for child in subworkflow.task_tree.children:
|
||||
for assignment in self.in_assign:
|
||||
assignment.assign(my_task, child)
|
||||
child.task_spec._update(child)
|
||||
# Instead of completing immediately, we'll wait for the subworkflow to complete
|
||||
my_task._set_state(TaskState.WAITING)
|
||||
return True
|
||||
|
||||
def _update_hook(self, my_task):
|
||||
|
||||
super()._update_hook(my_task)
|
||||
subworkflow = my_task._get_internal_data('subworkflow')
|
||||
if subworkflow is None:
|
||||
# On the first update, we have to create the subworkflow
|
||||
return True
|
||||
self._create_subworkflow(my_task)
|
||||
elif subworkflow.is_completed():
|
||||
# Then wait until it finishes to complete
|
||||
my_task.complete()
|
||||
|
||||
def _on_subworkflow_completed(self, subworkflow, my_task):
|
||||
|
|
|
@ -62,8 +62,7 @@ class ThreadMerge(Join):
|
|||
# task that did the conditional parallel split.
|
||||
split_task = my_task._find_ancestor_from_name(self.split_task)
|
||||
if split_task is None:
|
||||
msg = 'Join with %s, which was not reached' % self.split_task
|
||||
raise WorkflowException(msg, task_spec=self)
|
||||
raise WorkflowException(f'Join with %s, which was not reached {self.split_task}', task_spec=self)
|
||||
tasks = split_task.task_spec._get_activated_threads(split_task)
|
||||
|
||||
# The default threshold is the number of threads that were started.
|
||||
|
@ -105,8 +104,7 @@ class ThreadMerge(Join):
|
|||
my_task._set_state(TaskState.WAITING)
|
||||
return
|
||||
|
||||
split_task_spec = my_task.workflow.get_task_spec_from_name(
|
||||
self.split_task)
|
||||
split_task_spec = my_task.workflow.get_task_spec_from_name(self.split_task)
|
||||
split_task = my_task._find_ancestor(split_task_spec)
|
||||
|
||||
# Find the inbound task that was completed last.
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from builtins import range
|
||||
# Copyright (C) 2007 Samuel Abels
|
||||
#
|
||||
# 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.triggered = True
|
||||
|
||||
def _predict_hook(self, my_task):
|
||||
def _get_predicted_outputs(self, my_task):
|
||||
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 self.thread_starter is None:
|
||||
self.thread_starter = self.outputs[0]
|
||||
|
||||
# Predict the outputs.
|
||||
outputs = []
|
||||
for i in range(split_n):
|
||||
outputs.append(self.thread_starter)
|
||||
outputs = self._get_predicted_outputs(my_task)
|
||||
if my_task._is_definite():
|
||||
my_task._sync_children(outputs, TaskState.FUTURE)
|
||||
else:
|
||||
my_task._sync_children(outputs, TaskState.LIKELY)
|
||||
|
||||
def _on_complete_hook(self, my_task):
|
||||
# Split, and remember the number of splits in the context data.
|
||||
split_n = int(valueof(my_task, self.times))
|
||||
|
||||
# Create the outgoing tasks.
|
||||
outputs = []
|
||||
for i in range(split_n):
|
||||
outputs.append(self.thread_starter)
|
||||
def _run_hook(self, my_task):
|
||||
outputs = self._get_predicted_outputs(my_task)
|
||||
my_task._sync_children(outputs, TaskState.FUTURE)
|
||||
return True
|
||||
|
||||
def serialize(self, serializer):
|
||||
return serializer.serialize_thread_split(self)
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
from .base import TaskSpec
|
||||
from SpiffWorkflow.task import TaskState
|
||||
|
||||
|
||||
class ThreadStart(TaskSpec):
|
||||
|
@ -42,9 +43,10 @@ class ThreadStart(TaskSpec):
|
|||
TaskSpec.__init__(self, wf_spec, name, **kwargs)
|
||||
self.internal = True
|
||||
|
||||
def _on_complete_hook(self, my_task):
|
||||
def _run_hook(self, my_task):
|
||||
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):
|
||||
return serializer.serialize_thread_start(self)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from builtins import range
|
||||
# Copyright (C) 2007 Samuel Abels
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
|
@ -65,15 +64,14 @@ class Trigger(TaskSpec):
|
|||
self.queued += 1
|
||||
# All tasks that have already completed need to be put back to
|
||||
# READY.
|
||||
for thetask in my_task.workflow.task_tree:
|
||||
if thetask.thread_id != my_task.thread_id:
|
||||
for task in my_task.workflow.task_tree:
|
||||
if task.thread_id != my_task.thread_id:
|
||||
continue
|
||||
if (thetask.task_spec == self and
|
||||
thetask._has_state(TaskState.COMPLETED)):
|
||||
thetask._set_state(TaskState.FUTURE)
|
||||
thetask._ready()
|
||||
if task.task_spec == self and task._has_state(TaskState.COMPLETED):
|
||||
task._set_state(TaskState.FUTURE)
|
||||
task._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.
|
||||
|
||||
|
@ -85,10 +83,10 @@ class Trigger(TaskSpec):
|
|||
times = int(valueof(my_task, self.times, 1)) + self.queued
|
||||
for i in range(times):
|
||||
for task_name in self.context:
|
||||
task = my_task.workflow.get_task_spec_from_name(task_name)
|
||||
task._on_trigger(my_task)
|
||||
task_spec = my_task.workflow.get_task_spec_from_name(task_name)
|
||||
task_spec._on_trigger(my_task)
|
||||
self.queued = 0
|
||||
TaskSpec._on_complete_hook(self, my_task)
|
||||
return True
|
||||
|
||||
def serialize(self, serializer):
|
||||
return serializer.serialize_trigger(self)
|
||||
|
|
|
@ -74,10 +74,6 @@ class TaskSpec(object):
|
|||
:param wf_spec: A reference to the workflow specification that owns it.
|
||||
:type name: string
|
||||
: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
|
||||
:param manual: Whether this task requires a manual action to complete.
|
||||
:type data: dict((str, object))
|
||||
|
@ -107,7 +103,6 @@ class TaskSpec(object):
|
|||
self.defines = kwargs.get('defines', {})
|
||||
self.pre_assign = kwargs.get('pre_assign',[])
|
||||
self.post_assign = kwargs.get('post_assign', [])
|
||||
self.locks = kwargs.get('lock', [])
|
||||
self.lookahead = 2 # Maximum number of MAYBE predictions.
|
||||
|
||||
# Events.
|
||||
|
@ -213,7 +208,7 @@ class TaskSpec(object):
|
|||
if len(self.inputs) < 1:
|
||||
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.
|
||||
|
||||
|
@ -229,26 +224,25 @@ class TaskSpec(object):
|
|||
if seen is None:
|
||||
seen = []
|
||||
|
||||
self._predict_hook(my_task)
|
||||
if not my_task._is_definite():
|
||||
if my_task._has_state(mask):
|
||||
self._predict_hook(my_task)
|
||||
|
||||
if my_task._is_predicted():
|
||||
seen.append(self)
|
||||
|
||||
look_ahead = my_task._is_definite() or looked_ahead + 1 < self.lookahead
|
||||
for child in my_task.children:
|
||||
if not child._is_finished() and child not in seen and look_ahead:
|
||||
child.task_spec._predict(child, seen[:], looked_ahead + 1)
|
||||
if child._has_state(mask) and child not in seen and look_ahead:
|
||||
child.task_spec._predict(child, seen[:], looked_ahead + 1, mask)
|
||||
|
||||
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.
|
||||
if my_task._is_definite():
|
||||
if my_task._is_definite():
|
||||
best_state = TaskState.FUTURE
|
||||
else:
|
||||
best_state = my_task.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):
|
||||
"""
|
||||
|
@ -281,42 +275,13 @@ class TaskSpec(object):
|
|||
assert my_task is not None
|
||||
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.
|
||||
for assignment in self.pre_assign:
|
||||
assignment.assign(my_task, my_task)
|
||||
|
||||
# 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)
|
||||
|
||||
# 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
|
||||
self.reached_event.emit(my_task.workflow, my_task)
|
||||
|
||||
def _on_ready_hook(self, my_task):
|
||||
"""
|
||||
|
@ -327,6 +292,35 @@ class TaskSpec(object):
|
|||
"""
|
||||
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):
|
||||
"""
|
||||
May be called by another task to cancel the operation before it was
|
||||
|
@ -359,20 +353,12 @@ class TaskSpec(object):
|
|||
:rtype: boolean
|
||||
: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)
|
||||
for child in my_task.children:
|
||||
# Don't like this, but this is the most expedient way of preventing cancelled tasks from reactivation
|
||||
if child.state != TaskState.CANCELLED:
|
||||
if not child._is_finished():
|
||||
child.task_spec._update(child)
|
||||
my_task.workflow._task_completed_notify(my_task)
|
||||
|
||||
self.completed_event.emit(my_task.workflow, my_task)
|
||||
return True
|
||||
|
||||
def _on_complete_hook(self, my_task):
|
||||
"""
|
||||
|
@ -419,7 +405,6 @@ class TaskSpec(object):
|
|||
'defines':self.defines,
|
||||
'pre_assign':self.pre_assign,
|
||||
'post_assign':self.post_assign,
|
||||
'locks':self.locks,
|
||||
'lookahead':self.lookahead,
|
||||
}
|
||||
|
||||
|
@ -457,7 +442,6 @@ class TaskSpec(object):
|
|||
out.defines = s_state.get('defines')
|
||||
out.pre_assign = s_state.get('pre_assign')
|
||||
out.post_assign = s_state.get('post_assign')
|
||||
out.locks = s_state.get('locks')
|
||||
out.lookahead = s_state.get('lookahead')
|
||||
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.StartEvent import StartEvent
|
||||
from SpiffWorkflow.bpmn.specs.events.IntermediateEvent import IntermediateThrowEvent, BoundaryEvent, IntermediateCatchEvent
|
||||
|
||||
from SpiffWorkflow.spiff.specs.none_task import NoneTask
|
||||
from SpiffWorkflow.spiff.specs.manual_task import ManualTask
|
||||
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.service_task import ServiceTask
|
||||
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,
|
||||
SpiffIntermediateCatchEventParser, SpiffIntermediateThrowEventParser, SpiffSendTaskParser, SpiffReceiveTaskParser)
|
||||
from SpiffWorkflow.dmn.specs import BusinessRuleTask
|
||||
|
||||
|
||||
from SpiffWorkflow.spiff.parser.task_spec import BusinessRuleTaskParser
|
||||
|
||||
|
@ -44,4 +53,3 @@ class SpiffBpmnParser(BpmnDmnParser):
|
|||
full_tag('receiveTask'): (SpiffReceiveTaskParser, ReceiveTask),
|
||||
full_tag('businessRuleTask'): (BusinessRuleTaskParser, BusinessRuleTask)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
from lxml import etree
|
||||
|
||||
from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask
|
||||
from SpiffWorkflow.bpmn.parser.TaskParser import TaskParser
|
||||
from SpiffWorkflow.bpmn.parser.task_parsers import SubprocessParser
|
||||
from SpiffWorkflow.bpmn.parser.util import xpath_eval
|
||||
|
||||
from SpiffWorkflow.spiff.specs.multiinstance_task import StandardLoopTask, ParallelMultiInstanceTask, SequentialMultiInstanceTask
|
||||
from SpiffWorkflow.spiff.specs.business_rule_task import BusinessRuleTask
|
||||
|
||||
SPIFFWORKFLOW_MODEL_NS = 'http://spiffworkflow.org/bpmn/schema/1.0/core'
|
||||
SPIFFWORKFLOW_MODEL_PREFIX = 'spiffworkflow'
|
||||
|
@ -169,13 +169,19 @@ class BusinessRuleTaskParser(SpiffTaskParser):
|
|||
|
||||
def create_task(self):
|
||||
decision_ref = self.get_decision_ref(self.node)
|
||||
return BusinessRuleTask(self.spec,
|
||||
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)
|
||||
)
|
||||
extensions = self.parse_extensions()
|
||||
prescript = extensions.get('preScript')
|
||||
postscript = extensions.get('postScript')
|
||||
return BusinessRuleTask(
|
||||
self.spec,
|
||||
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
|
||||
def get_decision_ref(node):
|
||||
|
|
|
@ -5,7 +5,6 @@ from SpiffWorkflow.bpmn.serializer.task_spec import (
|
|||
SimpleTaskConverter,
|
||||
StartTaskConverter,
|
||||
EndJoinConverter,
|
||||
LoopResetTaskConverter,
|
||||
StartEventConverter,
|
||||
EndEventConverter,
|
||||
IntermediateCatchEventConverter,
|
||||
|
@ -32,6 +31,7 @@ from .task_spec import (
|
|||
CallActivityTaskConverter,
|
||||
ParallelMultiInstanceTaskConverter,
|
||||
SequentialMultiInstanceTaskConverter,
|
||||
BusinessRuleTaskConverter,
|
||||
)
|
||||
|
||||
from SpiffWorkflow.bpmn.serializer.event_definition import MessageEventDefinitionConverter as DefaultMessageEventDefinitionConverter
|
||||
|
@ -42,7 +42,6 @@ SPIFF_SPEC_CONFIG['task_specs'] = [
|
|||
SimpleTaskConverter,
|
||||
StartTaskConverter,
|
||||
EndJoinConverter,
|
||||
LoopResetTaskConverter,
|
||||
StartEventConverter,
|
||||
EndEventConverter,
|
||||
IntermediateCatchEventConverter,
|
||||
|
@ -66,6 +65,7 @@ SPIFF_SPEC_CONFIG['task_specs'] = [
|
|||
StandardLoopTaskConverter,
|
||||
ParallelMultiInstanceTaskConverter,
|
||||
SequentialMultiInstanceTaskConverter,
|
||||
BusinessRuleTaskConverter
|
||||
]
|
||||
SPIFF_SPEC_CONFIG['event_definitions'].remove(DefaultMessageEventDefinitionConverter)
|
||||
SPIFF_SPEC_CONFIG['event_definitions'].append(MessageEventDefinitionConverter)
|
|
@ -1,5 +1,6 @@
|
|||
from SpiffWorkflow.bpmn.serializer.helpers.spec import TaskSpecConverter
|
||||
from SpiffWorkflow.bpmn.serializer.task_spec import MultiInstanceTaskConverter
|
||||
from SpiffWorkflow.dmn.serializer.task_spec import BaseBusinessRuleTaskConverter
|
||||
|
||||
from SpiffWorkflow.spiff.specs.none_task import NoneTask
|
||||
from SpiffWorkflow.spiff.specs.manual_task import ManualTask
|
||||
|
@ -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.events.event_types import SendTask, ReceiveTask
|
||||
from SpiffWorkflow.spiff.specs.multiinstance_task import StandardLoopTask, ParallelMultiInstanceTask, SequentialMultiInstanceTask
|
||||
from SpiffWorkflow.spiff.specs.business_rule_task import BusinessRuleTask
|
||||
|
||||
|
||||
class SpiffBpmnTaskConverter(TaskSpecConverter):
|
||||
|
@ -39,6 +41,16 @@ class UserTaskConverter(SpiffBpmnTaskConverter):
|
|||
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):
|
||||
|
||||
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
|
||||
|
||||
FINISHED_MASK = CANCELLED | COMPLETED
|
||||
DEFINITE_MASK = FUTURE | WAITING | READY | FINISHED_MASK
|
||||
PREDICTED_MASK = FUTURE | LIKELY | MAYBE
|
||||
NOT_FINISHED_MASK = PREDICTED_MASK | WAITING | READY
|
||||
DEFINITE_MASK = FUTURE | WAITING | READY
|
||||
PREDICTED_MASK = LIKELY | MAYBE
|
||||
NOT_FINISHED_MASK = PREDICTED_MASK | DEFINITE_MASK
|
||||
ANY_MASK = FINISHED_MASK | NOT_FINISHED_MASK
|
||||
|
||||
|
||||
|
@ -292,43 +292,6 @@ class Task(object, metaclass=DeprecatedMetaTask):
|
|||
self.data = DeepMerge.merge(self.data, data)
|
||||
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):
|
||||
return Task.Iterator(self)
|
||||
|
||||
|
@ -366,9 +329,7 @@ class Task(object, metaclass=DeprecatedMetaTask):
|
|||
self.children.remove(task)
|
||||
|
||||
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
|
||||
|
||||
def _is_finished(self):
|
||||
|
@ -380,6 +341,43 @@ class Task(object, metaclass=DeprecatedMetaTask):
|
|||
def _is_definite(self):
|
||||
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):
|
||||
"""
|
||||
Adds a new child and assigns the given TaskSpec to it.
|
||||
|
@ -391,17 +389,60 @@ class Task(object, metaclass=DeprecatedMetaTask):
|
|||
:rtype: 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:
|
||||
msg = 'Attempt to add non-predicted child to predicted task'
|
||||
raise WorkflowException(msg, task_spec=self.task_spec)
|
||||
raise WorkflowException('Attempt to add non-predicted child to predicted task', task_spec=self.task_spec)
|
||||
task = Task(self.workflow, task_spec, self, state=state)
|
||||
task.thread_id = self.thread_id
|
||||
if state == TaskState.READY:
|
||||
task._ready()
|
||||
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):
|
||||
"""
|
||||
Assigns a new thread id to the task.
|
||||
|
@ -419,78 +460,6 @@ class Task(object, metaclass=DeprecatedMetaTask):
|
|||
child.thread_id = 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):
|
||||
"""
|
||||
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._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):
|
||||
return str(self.task_spec.name)
|
||||
|
||||
|
@ -590,14 +550,10 @@ class Task(object, metaclass=DeprecatedMetaTask):
|
|||
return str(self.task_spec.description)
|
||||
|
||||
def get_state_name(self):
|
||||
"""
|
||||
Returns a textual representation of this Task's state.
|
||||
"""
|
||||
state_name = []
|
||||
"""Returns a textual representation of this Task's state."""
|
||||
for state, name in list(TaskStateNames.items()):
|
||||
if self._has_state(state):
|
||||
state_name.append(name)
|
||||
return '|'.join(state_name)
|
||||
return name
|
||||
|
||||
def get_spec_data(self, name=None, default=None):
|
||||
"""
|
||||
|
@ -648,37 +604,54 @@ class Task(object, metaclass=DeprecatedMetaTask):
|
|||
"""
|
||||
return self.data.get(name, default)
|
||||
|
||||
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()
|
||||
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.CANCELLED)
|
||||
self._drop_children()
|
||||
self.task_spec._on_cancel(self)
|
||||
self._set_state(TaskState.READY)
|
||||
self.task_spec._on_ready(self)
|
||||
|
||||
def complete(self):
|
||||
def run(self):
|
||||
"""
|
||||
Called by the associated task to let us know that its state
|
||||
has changed (e.g. from FUTURE to COMPLETED.)
|
||||
Execute the task.
|
||||
|
||||
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()
|
||||
retval = self.task_spec._on_complete(self)
|
||||
retval = self.task_spec._run(self)
|
||||
extra = self.log_info({
|
||||
'action': 'Complete',
|
||||
'elapsed': time.time() - start
|
||||
})
|
||||
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
|
||||
|
||||
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):
|
||||
"""
|
||||
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:
|
||||
# python 2
|
||||
from mutex import mutex
|
||||
class mutex(object):
|
||||
|
||||
except ImportError:
|
||||
# python 3
|
||||
from threading import Lock
|
||||
def __init__(self):
|
||||
self.lock = Lock()
|
||||
|
||||
class mutex(object):
|
||||
def lock(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def __init__(self):
|
||||
self.lock = Lock()
|
||||
|
||||
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):
|
||||
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()
|
||||
|
|
|
@ -20,14 +20,14 @@
|
|||
import logging
|
||||
|
||||
from .specs.Simple import Simple
|
||||
from .specs.LoopResetTask import LoopResetTask
|
||||
from .task import Task, TaskState
|
||||
from .util.compat import mutex
|
||||
from .util.event import Event
|
||||
from .exceptions import WorkflowException
|
||||
from .exceptions import TaskNotFoundException, WorkflowException
|
||||
|
||||
logger = logging.getLogger('spiff')
|
||||
|
||||
|
||||
class Workflow(object):
|
||||
|
||||
"""
|
||||
|
@ -54,29 +54,25 @@ class Workflow(object):
|
|||
self.outer_workflow = kwargs.get('parent', self)
|
||||
self.locks = {}
|
||||
self.last_task = None
|
||||
if deserializing:
|
||||
assert 'Root' in workflow_spec.task_specs
|
||||
root = workflow_spec.task_specs['Root'] # Probably deserialized
|
||||
if 'Root' in workflow_spec.task_specs:
|
||||
root = workflow_spec.task_specs['Root']
|
||||
else:
|
||||
if 'Root' in workflow_spec.task_specs:
|
||||
root = workflow_spec.task_specs['Root']
|
||||
else:
|
||||
root = Simple(workflow_spec, 'Root')
|
||||
logger.info('Initialize', extra=self.log_info())
|
||||
root = Simple(workflow_spec, 'Root')
|
||||
|
||||
# Setting TaskState.COMPLETED prevents the root task from being executed.
|
||||
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.debug = False
|
||||
|
||||
# Events.
|
||||
self.completed_event = Event()
|
||||
|
||||
start = self.task_tree._add_child(self.spec.start, state=TaskState.FUTURE)
|
||||
|
||||
self.spec.start._predict(start)
|
||||
if 'parent' not in kwargs:
|
||||
start.task_spec._update(start)
|
||||
if not deserializing:
|
||||
self._predict()
|
||||
if 'parent' not in kwargs:
|
||||
start.task_spec._update(start)
|
||||
logger.info('Initialize', extra=self.log_info())
|
||||
|
||||
self.task_mapping = self._get_task_mapping()
|
||||
|
||||
|
@ -108,6 +104,10 @@ class Workflow(object):
|
|||
return True
|
||||
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):
|
||||
waiting = Task.Iterator(self.task_tree, TaskState.WAITING)
|
||||
return [w for w in waiting]
|
||||
|
@ -195,24 +195,6 @@ class Workflow(object):
|
|||
"""
|
||||
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):
|
||||
"""
|
||||
Returns all tasks whose spec has the given name.
|
||||
|
@ -222,15 +204,7 @@ class Workflow(object):
|
|||
:rtype: list[Task]
|
||||
:returns: A list of tasks that relate to the spec with the given name.
|
||||
"""
|
||||
return [task for task in self.get_tasks_iterator()
|
||||
if task.task_spec.name == name]
|
||||
|
||||
def empty(self,str):
|
||||
if str == None:
|
||||
return True
|
||||
if str == '':
|
||||
return True
|
||||
return False
|
||||
return [task for task in self.get_tasks_iterator() if task.task_spec.name == name]
|
||||
|
||||
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)]
|
||||
|
||||
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):
|
||||
"""
|
||||
Returns a iterator of Task objects with the given state.
|
||||
|
@ -286,22 +228,54 @@ class Workflow(object):
|
|||
"""
|
||||
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.
|
||||
|
||||
: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)
|
||||
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)
|
||||
task = self.get_task_from_id(task_id)
|
||||
return task.run()
|
||||
|
||||
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.
|
||||
Returns True if completed, False otherwise.
|
||||
|
@ -329,7 +303,7 @@ class Workflow(object):
|
|||
self.last_task = None
|
||||
if task is not None:
|
||||
if not (halt_on_manual and task.task_spec.manual):
|
||||
if task.complete():
|
||||
if task.run():
|
||||
self.last_task = task
|
||||
return True
|
||||
blacklist.append(task)
|
||||
|
@ -340,7 +314,7 @@ class Workflow(object):
|
|||
if task._is_descendant_of(blacklisted_task):
|
||||
continue
|
||||
if not (halt_on_manual and task.task_spec.manual):
|
||||
if task.complete():
|
||||
if task.run():
|
||||
self.last_task = task
|
||||
return True
|
||||
blacklist.append(task)
|
||||
|
@ -353,7 +327,7 @@ class Workflow(object):
|
|||
return True
|
||||
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
|
||||
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.
|
||||
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
|
||||
|
||||
def get_dump(self):
|
||||
|
|
|
@ -19,7 +19,7 @@ workflow = Workflow(spec)
|
|||
# Execute until all tasks are done or require manual intervention.
|
||||
# For the sake of this tutorial, we ignore the "manual" flag on the
|
||||
# tasks. In practice, you probably don't want to do that.
|
||||
workflow.complete_all(halt_on_manual=False)
|
||||
workflow.run_all(halt_on_manual=False)
|
||||
|
||||
# Alternatively, this is what a UI would do for a manual task.
|
||||
#workflow.complete_task_from_id(...)
|
||||
|
|
|
@ -21,7 +21,10 @@ setup(name='SpiffWorkflow',
|
|||
author_email='dan@sartography.com',
|
||||
license='lGPLv2',
|
||||
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',
|
||||
# required for python 3.7 - https://stackoverflow.com/a/73932581
|
||||
'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
|
||||
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
|
||||
from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase
|
||||
from .BpmnWorkflowTestCase import BpmnWorkflowTestCase
|
||||
|
||||
__author__ = 'matth'
|
||||
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from builtins import range
|
||||
import unittest
|
||||
import logging
|
||||
from SpiffWorkflow.task import TaskState
|
||||
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
|
||||
from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase
|
||||
|
||||
__author__ = 'matth'
|
||||
|
@ -28,13 +25,9 @@ class BaseParallelTestCase(BpmnWorkflowTestCase):
|
|||
"Doing step '%s' (with choice='%s')", s, choice)
|
||||
else:
|
||||
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()
|
||||
if save_restore:
|
||||
# logging.debug("Before SaveRestore: \n%s" %
|
||||
# self.workflow.get_dump())
|
||||
self.save_restore()
|
||||
|
||||
self.workflow.do_engine_steps()
|
||||
|
|
|
@ -34,7 +34,7 @@ class TestUserTask(UserTask):
|
|||
|
||||
def do_choice(self, task, choice):
|
||||
task.set_data(choice=choice)
|
||||
task.complete()
|
||||
task.run()
|
||||
|
||||
|
||||
class TestExclusiveGatewayParser(ConditionalGatewayParser):
|
||||
|
|
|
@ -64,7 +64,7 @@ class BpmnWorkflowTestCase(unittest.TestCase):
|
|||
def switch_workflow(p):
|
||||
for task_id, sp in p.workflow._get_outermost_workflow().subprocesses.items():
|
||||
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):
|
||||
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:
|
||||
tasks[0].set_data(**set_attribs)
|
||||
tasks[0].complete()
|
||||
tasks[0].run()
|
||||
|
||||
def save_restore(self):
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ from SpiffWorkflow.bpmn.PythonScriptEngine import PythonScriptEngine
|
|||
|
||||
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
|
||||
from SpiffWorkflow.exceptions import WorkflowTaskException
|
||||
from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase
|
||||
from .BpmnWorkflowTestCase import BpmnWorkflowTestCase
|
||||
|
||||
__author__ = 'kellym'
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ class CollaborationTest(BpmnWorkflowTestCase):
|
|||
workflow.do_engine_steps()
|
||||
for idx, task in enumerate(workflow.get_ready_user_tasks()):
|
||||
task.data['task_num'] = idx
|
||||
task.complete()
|
||||
task.run()
|
||||
workflow.do_engine_steps()
|
||||
ready_tasks = workflow.get_ready_user_tasks()
|
||||
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
|
||||
for task in ready_tasks:
|
||||
task.data.update(init_id=task.data['task_num'])
|
||||
task.complete()
|
||||
task.run()
|
||||
workflow.do_engine_steps()
|
||||
# If the messages were routed properly, the id should match
|
||||
for task in workflow.get_tasks_from_spec_name('subprocess_end'):
|
||||
|
@ -108,7 +108,7 @@ class CollaborationTest(BpmnWorkflowTestCase):
|
|||
workflow.do_engine_steps()
|
||||
for idx, task in enumerate(workflow.get_ready_user_tasks()):
|
||||
task.data['task_num'] = idx
|
||||
task.complete()
|
||||
task.run()
|
||||
workflow.do_engine_steps()
|
||||
|
||||
# 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
|
||||
for task in ready_tasks:
|
||||
task.data.update(init_id=task.data['task_num'])
|
||||
task.complete()
|
||||
task.run()
|
||||
workflow.do_engine_steps()
|
||||
|
||||
# Complete dummy tasks
|
||||
for task in workflow.get_ready_user_tasks():
|
||||
task.complete()
|
||||
task.run()
|
||||
workflow.do_engine_steps()
|
||||
|
||||
# Repeat for the other process, using a different mapped name
|
||||
|
@ -136,7 +136,7 @@ class CollaborationTest(BpmnWorkflowTestCase):
|
|||
self.assertEqual(len(waiting), 2)
|
||||
for task in ready_tasks:
|
||||
task.data.update(subprocess=task.data['task_num'])
|
||||
task.complete()
|
||||
task.run()
|
||||
workflow.do_engine_steps()
|
||||
|
||||
# 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
|
||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||
ready_tasks[0].data = { 'obj_1': 'hello' }
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
|
||||
# Remove the data before advancing
|
||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||
self.workflow.data.pop('obj_1')
|
||||
with self.assertRaises(WorkflowDataException) as exc:
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
self.assertEqual(exc.data_output.name, 'obj_1')
|
||||
|
||||
def testMissingDataOutput(self):
|
||||
|
@ -37,7 +37,7 @@ class DataObjectReferenceTest(BpmnWorkflowTestCase):
|
|||
self.workflow.do_engine_steps()
|
||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||
with self.assertRaises(WorkflowDataException) as exc:
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
self.assertEqual(exc.data_output.name, 'obj_1')
|
||||
|
||||
def actual_test(self, save_restore):
|
||||
|
@ -48,7 +48,7 @@ class DataObjectReferenceTest(BpmnWorkflowTestCase):
|
|||
# Set up the data
|
||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||
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
|
||||
self.assertNotIn('obj_1', ready_tasks[0].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
|
||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||
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
|
||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||
self.assertEqual(ready_tasks[0].data['obj_1'], 'hello')
|
||||
# Modify the value in the task
|
||||
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
|
||||
# and be removed from the task data
|
||||
self.assertNotIn('obj_1', ready_tasks[0].data)
|
||||
|
@ -77,7 +77,7 @@ class DataObjectReferenceTest(BpmnWorkflowTestCase):
|
|||
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||
self.assertEqual(ready_tasks[0].data['obj_1'], 'hello')
|
||||
ready_tasks[0].data['obj_1'] = 'hello again'
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
self.workflow.do_engine_steps()
|
||||
sp = self.workflow.get_tasks_from_spec_name('subprocess')[0]
|
||||
# It was copied out
|
||||
|
|
|
@ -76,7 +76,7 @@ class CallActivityDataTest(BpmnWorkflowTestCase):
|
|||
waiting = self.workflow.get_tasks(TaskState.WAITING)
|
||||
while len(waiting) == 0:
|
||||
next_task = self.workflow.get_tasks(TaskState.READY)[0]
|
||||
next_task.complete()
|
||||
next_task.run()
|
||||
waiting = self.workflow.get_tasks(TaskState.WAITING)
|
||||
|
||||
def complete_subprocess(self):
|
||||
|
@ -84,7 +84,7 @@ class CallActivityDataTest(BpmnWorkflowTestCase):
|
|||
waiting = self.workflow.get_tasks(TaskState.WAITING)
|
||||
while len(waiting) > 0:
|
||||
next_task = self.workflow.get_tasks(TaskState.READY)[0]
|
||||
next_task.complete()
|
||||
next_task.run()
|
||||
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.data.update({'out_1': 1})
|
||||
with self.assertRaises(WorkflowDataException) as exc:
|
||||
task.complete()
|
||||
task.run()
|
||||
self.assertEqual(exc.exception.data_output.name, 'out_2')
|
||||
|
||||
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]
|
||||
self.assertDictEqual(task.data, {'in_1': 1, 'in_2': 'hello world'})
|
||||
task.data.update({'out_1': 1, 'out_2': 'bye', 'extra': True})
|
||||
task.complete()
|
||||
task.run()
|
||||
self.workflow.do_engine_steps()
|
||||
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):
|
||||
task = self.workflow.get_ready_user_tasks()[0]
|
||||
task.data = value
|
||||
task.complete()
|
||||
task.run()
|
||||
|
|
|
@ -26,7 +26,7 @@ class BaseTestCase(BpmnWorkflowTestCase):
|
|||
self.assertEqual(task.task_spec.name, 'any_task [child]')
|
||||
self.assertIn('input_item', task.data)
|
||||
task.data['output_item'] = task.data['input_item'] * 2
|
||||
task.complete()
|
||||
task.run()
|
||||
if save_restore:
|
||||
self.save_restore()
|
||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||
|
@ -47,7 +47,7 @@ class BaseTestCase(BpmnWorkflowTestCase):
|
|||
self.assertEqual(len(ready_tasks), 3)
|
||||
task = [t for t in ready_tasks if t.data['input_item'] == 2][0]
|
||||
task.data['output_item'] = task.data['input_item'] * 2
|
||||
task.complete()
|
||||
task.run()
|
||||
self.workflow.do_engine_steps()
|
||||
self.workflow.refresh_waiting_tasks()
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ class ParallelOrderTest(BpmnWorkflowTestCase):
|
|||
|
||||
self.workflow.do_engine_steps()
|
||||
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()
|
||||
self.assertEquals("Task 1", tasks[0].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(
|
||||
'Repeated Task', ready_tasks[0].task_spec.description)
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
self.workflow.do_engine_steps()
|
||||
# The inclusive gateway allows us through here, because there is no route for the other thread
|
||||
# that doesn't use the same sequence flow
|
||||
|
@ -82,7 +82,7 @@ class ParallelThroughSameTaskTest(BpmnWorkflowTestCase):
|
|||
self.assertEqual(2, len(ready_tasks))
|
||||
self.assertEqual(
|
||||
'Repeated Task', ready_tasks[0].task_spec.description)
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
self.workflow.do_engine_steps()
|
||||
self.save_restore()
|
||||
# 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()
|
||||
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()
|
||||
task = self.workflow.get_ready_user_tasks()[0]
|
||||
self.save_restore()
|
||||
|
@ -50,11 +50,11 @@ class ResetSubProcessTest(BpmnWorkflowTestCase):
|
|||
self.workflow.do_engine_steps()
|
||||
self.assertEqual(1, len(self.workflow.get_ready_user_tasks()))
|
||||
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()
|
||||
task = self.workflow.get_ready_user_tasks()[0]
|
||||
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()
|
||||
task = self.workflow.get_tasks_from_spec_name('Task1')[0]
|
||||
task.reset_token(self.workflow.last_task.data)
|
||||
|
@ -62,19 +62,19 @@ class ResetSubProcessTest(BpmnWorkflowTestCase):
|
|||
self.reload_save_restore()
|
||||
task = self.workflow.get_ready_user_tasks()[0]
|
||||
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()
|
||||
task = self.workflow.get_ready_user_tasks()[0]
|
||||
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()
|
||||
task = self.workflow.get_ready_user_tasks()[0]
|
||||
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()
|
||||
task = self.workflow.get_ready_user_tasks()[0]
|
||||
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.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.assertIn('input_item', task.data)
|
||||
task.data['output_item'] = task.data['input_item'] * 2
|
||||
task.complete()
|
||||
task.run()
|
||||
if save_restore:
|
||||
self.save_restore()
|
||||
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.assertIn('input_item', ready.data)
|
||||
ready.data['output_item'] = ready.data['input_item'] * 2
|
||||
ready.complete()
|
||||
ready.run()
|
||||
self.workflow.do_engine_steps()
|
||||
self.workflow.refresh_waiting_tasks()
|
||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||
|
|
|
@ -21,7 +21,7 @@ class StandardLoopTest(BpmnWorkflowTestCase):
|
|||
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||
self.assertEqual(len(ready_tasks), 1)
|
||||
ready_tasks[0].data[str(idx)] = True
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
|
||||
self.workflow.do_engine_steps()
|
||||
self.assertTrue(self.workflow.is_completed())
|
||||
|
@ -36,7 +36,7 @@ class StandardLoopTest(BpmnWorkflowTestCase):
|
|||
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||
self.assertEqual(len(ready_tasks), 1)
|
||||
ready_tasks[0].data['done'] = True
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
|
||||
self.workflow.do_engine_steps()
|
||||
self.assertTrue(self.workflow.is_completed())
|
||||
|
|
|
@ -35,7 +35,7 @@ class SwimLaneTest(BpmnWorkflowTestCase):
|
|||
self.assertEqual(0, len(btasks))
|
||||
task = atasks[0]
|
||||
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()
|
||||
atasks = self.workflow.get_ready_user_tasks(lane="A")
|
||||
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
|
||||
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()
|
||||
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()
|
||||
|
||||
# Assert we are in lane C
|
||||
|
@ -56,7 +56,7 @@ class SwimLaneTest(BpmnWorkflowTestCase):
|
|||
self.assertEqual(tasks[0].task_spec.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()
|
||||
tasks = self.workflow.get_ready_user_tasks()
|
||||
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"?>
|
||||
<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">
|
||||
<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">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff"/>
|
||||
</extensionElements>
|
||||
</participant>
|
||||
</collaboration>
|
||||
<process id="sid-41eb2b6c-08bc-4a61-b38b-5f32052139c5" isClosed="false" isExecutable="false" name="Parallel Looping After Join" processType="None">
|
||||
<laneSet id="sid-127ca06b-aba8-45aa-bd8a-7be70ec94b2c">
|
||||
<lane id="sid-72009BCE-46B4-4B4B-AEAE-1E7199522142" name="Tester">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue=""/>
|
||||
</extensionElements>
|
||||
<flowNodeRef>sid-B33EE043-AB93-4343-A1D4-7B267E2DAFBE</flowNodeRef>
|
||||
<flowNodeRef>sid-349F8C0C-45EA-489C-84DD-1D944F48D778</flowNodeRef>
|
||||
<flowNodeRef>sid-57463471-693A-42A2-9EC6-6460BEDECA86</flowNodeRef>
|
||||
<flowNodeRef>sid-CA089240-802A-4C32-9130-FB1A33DDCCC3</flowNodeRef>
|
||||
<flowNodeRef>sid-E976FBC2-266E-420F-8D4D-C8FBC6199090</flowNodeRef>
|
||||
<flowNodeRef>sid-F3A979E3-F586-4807-8223-1FAB5A5647B0</flowNodeRef>
|
||||
<flowNodeRef>sid-51816945-79BF-47F9-BA3C-E95ABAE3D1DB</flowNodeRef>
|
||||
<flowNodeRef>sid-4F7F3AA6-4E8E-458D-BBEF-E03FC0646897</flowNodeRef>
|
||||
<flowNodeRef>sid-ABD788A3-CD57-4280-A22A-260B3AEEE138</flowNodeRef>
|
||||
<flowNodeRef>sid-E7B8898A-0D14-4E98-B3D7-736B94EE3FA7</flowNodeRef>
|
||||
<flowNodeRef>sid-A1609BD5-1E4A-47AE-8648-1DD41D1B1D58</flowNodeRef>
|
||||
<flowNodeRef>sid-1946C635-7886-4687-844F-C644FA6222B8</flowNodeRef>
|
||||
<flowNodeRef>sid-4C3B3C16-91DB-43E3-A816-FFEE572E61E1</flowNodeRef>
|
||||
<flowNodeRef>sid-55C018B8-C073-4292-9ED0-79BDE50E7498</flowNodeRef>
|
||||
<flowNodeRef>sid-E7904BFA-1F17-478E-91C9-C8A5B64190C9</flowNodeRef>
|
||||
</lane>
|
||||
</laneSet>
|
||||
<startEvent id="sid-B33EE043-AB93-4343-A1D4-7B267E2DAFBE" name="">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff"/>
|
||||
</extensionElements>
|
||||
<outgoing>sid-F3994F51-FE54-4910-A1F4-E5895AA1A612</outgoing>
|
||||
</startEvent>
|
||||
<parallelGateway gatewayDirection="Diverging" id="sid-349F8C0C-45EA-489C-84DD-1D944F48D778" name="First Split">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff"/>
|
||||
</extensionElements>
|
||||
<incoming>sid-3E0EBE59-75C8-465C-90CC-197CE808A96E</incoming>
|
||||
<outgoing>sid-7E15C71B-DE9E-4788-B140-A647C99FDC94</outgoing>
|
||||
<outgoing>sid-B6E22A74-A691-453A-A789-B9F8AF787D7C</outgoing>
|
||||
</parallelGateway>
|
||||
<task completionQuantity="1" id="sid-57463471-693A-42A2-9EC6-6460BEDECA86" isForCompensation="false" name="1" startQuantity="1">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc"/>
|
||||
</extensionElements>
|
||||
<incoming>sid-7E15C71B-DE9E-4788-B140-A647C99FDC94</incoming>
|
||||
<outgoing>sid-607CB05E-8762-41B6-AD43-C3970661A99D</outgoing>
|
||||
</task>
|
||||
<task completionQuantity="1" id="sid-CA089240-802A-4C32-9130-FB1A33DDCCC3" isForCompensation="false" name="2" startQuantity="1">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc"/>
|
||||
</extensionElements>
|
||||
<incoming>sid-B6E22A74-A691-453A-A789-B9F8AF787D7C</incoming>
|
||||
<outgoing>sid-CAEAD081-6E73-4C98-8656-C67DA18F5140</outgoing>
|
||||
</task>
|
||||
<parallelGateway gatewayDirection="Converging" id="sid-E976FBC2-266E-420F-8D4D-C8FBC6199090" name="Join of First Split">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff"/>
|
||||
</extensionElements>
|
||||
<incoming>sid-231F8A51-752F-4CB3-8FD1-23D153238344</incoming>
|
||||
<incoming>sid-607CB05E-8762-41B6-AD43-C3970661A99D</incoming>
|
||||
<outgoing>sid-0895E09C-077C-4D12-8C11-31F28CBC7740</outgoing>
|
||||
</parallelGateway>
|
||||
<task completionQuantity="1" id="sid-F3A979E3-F586-4807-8223-1FAB5A5647B0" isForCompensation="false" name="Retry?" startQuantity="1">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc"/>
|
||||
</extensionElements>
|
||||
<incoming>sid-0895E09C-077C-4D12-8C11-31F28CBC7740</incoming>
|
||||
<outgoing>sid-2668AC98-39E4-4B12-9052-930528086CAC</outgoing>
|
||||
</task>
|
||||
<endEvent id="sid-51816945-79BF-47F9-BA3C-E95ABAE3D1DB" name="">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff"/>
|
||||
</extensionElements>
|
||||
<incoming>sid-F6160C0E-216C-4D72-98D1-CC5549327D55</incoming>
|
||||
</endEvent>
|
||||
<parallelGateway gatewayDirection="Diverging" id="sid-4F7F3AA6-4E8E-458D-BBEF-E03FC0646897" name="Second Split">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff"/>
|
||||
</extensionElements>
|
||||
<incoming>sid-CAEAD081-6E73-4C98-8656-C67DA18F5140</incoming>
|
||||
<outgoing>sid-918C653D-0960-4223-9C28-78114F238BCC</outgoing>
|
||||
<outgoing>sid-FD82C2A6-7C54-4890-901E-A7E864F7605C</outgoing>
|
||||
</parallelGateway>
|
||||
<task completionQuantity="1" id="sid-ABD788A3-CD57-4280-A22A-260B3AEEE138" isForCompensation="false" name="2A" startQuantity="1">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc"/>
|
||||
</extensionElements>
|
||||
<incoming>sid-918C653D-0960-4223-9C28-78114F238BCC</incoming>
|
||||
<outgoing>sid-961AF51C-9935-410E-AAA4-105B19186F5E</outgoing>
|
||||
</task>
|
||||
<task completionQuantity="1" id="sid-E7B8898A-0D14-4E98-B3D7-736B94EE3FA7" isForCompensation="false" name="2B" startQuantity="1">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc"/>
|
||||
</extensionElements>
|
||||
<incoming>sid-FD82C2A6-7C54-4890-901E-A7E864F7605C</incoming>
|
||||
<outgoing>sid-47947925-21CD-46FF-8D3F-294B235AA4CF</outgoing>
|
||||
</task>
|
||||
<parallelGateway gatewayDirection="Converging" id="sid-A1609BD5-1E4A-47AE-8648-1DD41D1B1D58" name="Join of Second Split">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff"/>
|
||||
</extensionElements>
|
||||
<incoming>sid-961AF51C-9935-410E-AAA4-105B19186F5E</incoming>
|
||||
<incoming>sid-47947925-21CD-46FF-8D3F-294B235AA4CF</incoming>
|
||||
<outgoing>sid-AFA38469-CD5C-42A2-9473-2EAEBA61F0C0</outgoing>
|
||||
</parallelGateway>
|
||||
<task completionQuantity="1" id="sid-1946C635-7886-4687-844F-C644FA6222B8" isForCompensation="false" name="2 Done" startQuantity="1">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc"/>
|
||||
</extensionElements>
|
||||
<incoming>sid-AFA38469-CD5C-42A2-9473-2EAEBA61F0C0</incoming>
|
||||
<outgoing>sid-231F8A51-752F-4CB3-8FD1-23D153238344</outgoing>
|
||||
</task>
|
||||
<exclusiveGateway gatewayDirection="Diverging" id="sid-4C3B3C16-91DB-43E3-A816-FFEE572E61E1" name="">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff"/>
|
||||
</extensionElements>
|
||||
<incoming>sid-2668AC98-39E4-4B12-9052-930528086CAC</incoming>
|
||||
<outgoing>sid-08D6385B-C6BB-45FC-A6BD-2369F392868D</outgoing>
|
||||
<outgoing>sid-41205B5D-4DBA-4155-A0EE-7D71CE9AA459</outgoing>
|
||||
</exclusiveGateway>
|
||||
<task completionQuantity="1" id="sid-55C018B8-C073-4292-9ED0-79BDE50E7498" isForCompensation="false" name="Done" startQuantity="1">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc"/>
|
||||
</extensionElements>
|
||||
<incoming>sid-08D6385B-C6BB-45FC-A6BD-2369F392868D</incoming>
|
||||
<outgoing>sid-F6160C0E-216C-4D72-98D1-CC5549327D55</outgoing>
|
||||
</task>
|
||||
<task completionQuantity="1" id="sid-E7904BFA-1F17-478E-91C9-C8A5B64190C9" isForCompensation="false" name="Go" startQuantity="1">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc"/>
|
||||
</extensionElements>
|
||||
<incoming>sid-41205B5D-4DBA-4155-A0EE-7D71CE9AA459</incoming>
|
||||
<incoming>sid-F3994F51-FE54-4910-A1F4-E5895AA1A612</incoming>
|
||||
<outgoing>sid-3E0EBE59-75C8-465C-90CC-197CE808A96E</outgoing>
|
||||
</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-B6E22A74-A691-453A-A789-B9F8AF787D7C" name="" sourceRef="sid-349F8C0C-45EA-489C-84DD-1D944F48D778" targetRef="sid-CA089240-802A-4C32-9130-FB1A33DDCCC3"/>
|
||||
<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-CAEAD081-6E73-4C98-8656-C67DA18F5140" name="" sourceRef="sid-CA089240-802A-4C32-9130-FB1A33DDCCC3" targetRef="sid-4F7F3AA6-4E8E-458D-BBEF-E03FC0646897"/>
|
||||
<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-FD82C2A6-7C54-4890-901E-A7E864F7605C" name="" sourceRef="sid-4F7F3AA6-4E8E-458D-BBEF-E03FC0646897" targetRef="sid-E7B8898A-0D14-4E98-B3D7-736B94EE3FA7"/>
|
||||
<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-47947925-21CD-46FF-8D3F-294B235AA4CF" name="" sourceRef="sid-E7B8898A-0D14-4E98-B3D7-736B94EE3FA7" targetRef="sid-A1609BD5-1E4A-47AE-8648-1DD41D1B1D58"/>
|
||||
<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-231F8A51-752F-4CB3-8FD1-23D153238344" name="" sourceRef="sid-1946C635-7886-4687-844F-C644FA6222B8" targetRef="sid-E976FBC2-266E-420F-8D4D-C8FBC6199090"/>
|
||||
<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="sid-2668AC98-39E4-4B12-9052-930528086CAC" name="" sourceRef="sid-F3A979E3-F586-4807-8223-1FAB5A5647B0" targetRef="sid-4C3B3C16-91DB-43E3-A816-FFEE572E61E1"/>
|
||||
<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-F6160C0E-216C-4D72-98D1-CC5549327D55" name="" sourceRef="sid-55C018B8-C073-4292-9ED0-79BDE50E7498" targetRef="sid-51816945-79BF-47F9-BA3C-E95ABAE3D1DB"/>
|
||||
<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-3E0EBE59-75C8-465C-90CC-197CE808A96E" name="" sourceRef="sid-E7904BFA-1F17-478E-91C9-C8A5B64190C9" targetRef="sid-349F8C0C-45EA-489C-84DD-1D944F48D778"/>
|
||||
<sequenceFlow id="sid-F3994F51-FE54-4910-A1F4-E5895AA1A612" name="" sourceRef="sid-B33EE043-AB93-4343-A1D4-7B267E2DAFBE" targetRef="sid-E7904BFA-1F17-478E-91C9-C8A5B64190C9"/>
|
||||
</process>
|
||||
<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:BPMNShape bpmnElement="sid-B2E5AD50-035A-4CE8-B8A3-B175A6767B00" id="sid-B2E5AD50-035A-4CE8-B8A3-B175A6767B00_gui" isHorizontal="true">
|
||||
<omgdc:Bounds height="619.0" width="794.0" x="120.0" y="90.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="sid-72009BCE-46B4-4B4B-AEAE-1E7199522142" id="sid-72009BCE-46B4-4B4B-AEAE-1E7199522142_gui" isHorizontal="true">
|
||||
<omgdc:Bounds height="619.0" width="764.0" x="150.0" y="90.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="sid-B33EE043-AB93-4343-A1D4-7B267E2DAFBE" id="sid-B33EE043-AB93-4343-A1D4-7B267E2DAFBE_gui">
|
||||
<omgdc:Bounds height="30.0" width="30.0" x="190.0" y="122.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="sid-349F8C0C-45EA-489C-84DD-1D944F48D778" id="sid-349F8C0C-45EA-489C-84DD-1D944F48D778_gui">
|
||||
<omgdc:Bounds height="40.0" width="40.0" x="374.0" y="180.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="sid-57463471-693A-42A2-9EC6-6460BEDECA86" id="sid-57463471-693A-42A2-9EC6-6460BEDECA86_gui">
|
||||
<omgdc:Bounds height="80.0" width="100.0" x="510.0" y="214.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="sid-CA089240-802A-4C32-9130-FB1A33DDCCC3" id="sid-CA089240-802A-4C32-9130-FB1A33DDCCC3_gui">
|
||||
<omgdc:Bounds height="80.0" width="100.0" x="231.0" y="214.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="sid-E976FBC2-266E-420F-8D4D-C8FBC6199090" id="sid-E976FBC2-266E-420F-8D4D-C8FBC6199090_gui">
|
||||
<omgdc:Bounds height="40.0" width="40.0" x="540.0" y="405.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="sid-F3A979E3-F586-4807-8223-1FAB5A5647B0" id="sid-F3A979E3-F586-4807-8223-1FAB5A5647B0_gui">
|
||||
<omgdc:Bounds height="80.0" width="100.0" x="615.0" y="385.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="sid-51816945-79BF-47F9-BA3C-E95ABAE3D1DB" id="sid-51816945-79BF-47F9-BA3C-E95ABAE3D1DB_gui">
|
||||
<omgdc:Bounds height="28.0" width="28.0" x="780.0" y="630.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="sid-4F7F3AA6-4E8E-458D-BBEF-E03FC0646897" id="sid-4F7F3AA6-4E8E-458D-BBEF-E03FC0646897_gui">
|
||||
<omgdc:Bounds height="40.0" width="40.0" x="261.0" y="338.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="sid-ABD788A3-CD57-4280-A22A-260B3AEEE138" id="sid-ABD788A3-CD57-4280-A22A-260B3AEEE138_gui">
|
||||
<omgdc:Bounds height="80.0" width="100.0" x="177.0" y="417.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="sid-E7B8898A-0D14-4E98-B3D7-736B94EE3FA7" id="sid-E7B8898A-0D14-4E98-B3D7-736B94EE3FA7_gui">
|
||||
<omgdc:Bounds height="80.0" width="100.0" x="344.0" y="408.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="sid-A1609BD5-1E4A-47AE-8648-1DD41D1B1D58" id="sid-A1609BD5-1E4A-47AE-8648-1DD41D1B1D58_gui">
|
||||
<omgdc:Bounds height="40.0" width="40.0" x="285.0" y="555.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="sid-1946C635-7886-4687-844F-C644FA6222B8" id="sid-1946C635-7886-4687-844F-C644FA6222B8_gui">
|
||||
<omgdc:Bounds height="80.0" width="100.0" x="420.0" y="535.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="sid-4C3B3C16-91DB-43E3-A816-FFEE572E61E1" id="sid-4C3B3C16-91DB-43E3-A816-FFEE572E61E1_gui" isMarkerVisible="true">
|
||||
<omgdc:Bounds height="40.0" width="40.0" x="774.0" y="405.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="sid-55C018B8-C073-4292-9ED0-79BDE50E7498" id="sid-55C018B8-C073-4292-9ED0-79BDE50E7498_gui">
|
||||
<omgdc:Bounds height="80.0" width="100.0" x="744.0" y="497.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="sid-E7904BFA-1F17-478E-91C9-C8A5B64190C9" id="sid-E7904BFA-1F17-478E-91C9-C8A5B64190C9_gui">
|
||||
<omgdc:Bounds height="80.0" width="100.0" x="467.0" y="105.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge bpmnElement="sid-7E15C71B-DE9E-4788-B140-A647C99FDC94" id="sid-7E15C71B-DE9E-4788-B140-A647C99FDC94_gui">
|
||||
<omgdi:waypoint x="414.0" y="206.0"/>
|
||||
<omgdi:waypoint x="510.0" y="238.0"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="sid-08D6385B-C6BB-45FC-A6BD-2369F392868D" id="sid-08D6385B-C6BB-45FC-A6BD-2369F392868D_gui">
|
||||
<omgdi:waypoint x="794.0" y="445.0"/>
|
||||
<omgdi:waypoint x="794.0" y="497.0"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="sid-F3994F51-FE54-4910-A1F4-E5895AA1A612" id="sid-F3994F51-FE54-4910-A1F4-E5895AA1A612_gui">
|
||||
<omgdi:waypoint x="220.0" y="137.0"/>
|
||||
<omgdi:waypoint x="394.0" y="137.0"/>
|
||||
<omgdi:waypoint x="467.0" y="142.0"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="sid-231F8A51-752F-4CB3-8FD1-23D153238344" id="sid-231F8A51-752F-4CB3-8FD1-23D153238344_gui">
|
||||
<omgdi:waypoint x="475.0" y="535.0"/>
|
||||
<omgdi:waypoint x="491.0" y="425.0"/>
|
||||
<omgdi:waypoint x="540.0" y="425.0"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="sid-F6160C0E-216C-4D72-98D1-CC5549327D55" id="sid-F6160C0E-216C-4D72-98D1-CC5549327D55_gui">
|
||||
<omgdi:waypoint x="794.0" y="577.0"/>
|
||||
<omgdi:waypoint x="794.0" y="630.0"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="sid-B6E22A74-A691-453A-A789-B9F8AF787D7C" id="sid-B6E22A74-A691-453A-A789-B9F8AF787D7C_gui">
|
||||
<omgdi:waypoint x="394.0" y="220.0"/>
|
||||
<omgdi:waypoint x="394.5" y="254.0"/>
|
||||
<omgdi:waypoint x="331.0" y="254.0"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="sid-2668AC98-39E4-4B12-9052-930528086CAC" id="sid-2668AC98-39E4-4B12-9052-930528086CAC_gui">
|
||||
<omgdi:waypoint x="715.0" y="425.0"/>
|
||||
<omgdi:waypoint x="774.0" y="425.0"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="sid-CAEAD081-6E73-4C98-8656-C67DA18F5140" id="sid-CAEAD081-6E73-4C98-8656-C67DA18F5140_gui">
|
||||
<omgdi:waypoint x="281.0" y="294.0"/>
|
||||
<omgdi:waypoint x="281.0" y="338.0"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="sid-918C653D-0960-4223-9C28-78114F238BCC" id="sid-918C653D-0960-4223-9C28-78114F238BCC_gui">
|
||||
<omgdi:waypoint x="261.0" y="358.0"/>
|
||||
<omgdi:waypoint x="227.0" y="358.5"/>
|
||||
<omgdi:waypoint x="227.0" y="417.0"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="sid-FD82C2A6-7C54-4890-901E-A7E864F7605C" id="sid-FD82C2A6-7C54-4890-901E-A7E864F7605C_gui">
|
||||
<omgdi:waypoint x="301.0" y="358.0"/>
|
||||
<omgdi:waypoint x="394.0" y="358.5"/>
|
||||
<omgdi:waypoint x="394.0" y="408.0"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="sid-41205B5D-4DBA-4155-A0EE-7D71CE9AA459" id="sid-41205B5D-4DBA-4155-A0EE-7D71CE9AA459_gui">
|
||||
<omgdi:waypoint x="794.0" y="405.0"/>
|
||||
<omgdi:waypoint x="794.5" y="145.0"/>
|
||||
<omgdi:waypoint x="567.0" y="145.0"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="sid-607CB05E-8762-41B6-AD43-C3970661A99D" id="sid-607CB05E-8762-41B6-AD43-C3970661A99D_gui">
|
||||
<omgdi:waypoint x="560.0" y="294.0"/>
|
||||
<omgdi:waypoint x="560.0" y="405.0"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="sid-AFA38469-CD5C-42A2-9473-2EAEBA61F0C0" id="sid-AFA38469-CD5C-42A2-9473-2EAEBA61F0C0_gui">
|
||||
<omgdi:waypoint x="325.0" y="575.0"/>
|
||||
<omgdi:waypoint x="420.0" y="575.0"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="sid-961AF51C-9935-410E-AAA4-105B19186F5E" id="sid-961AF51C-9935-410E-AAA4-105B19186F5E_gui">
|
||||
<omgdi:waypoint x="277.0" y="457.0"/>
|
||||
<omgdi:waypoint x="305.0" y="457.0"/>
|
||||
<omgdi:waypoint x="305.0" y="555.0"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="sid-3E0EBE59-75C8-465C-90CC-197CE808A96E" id="sid-3E0EBE59-75C8-465C-90CC-197CE808A96E_gui">
|
||||
<omgdi:waypoint x="467.0" y="167.0"/>
|
||||
<omgdi:waypoint x="414.0" y="191.0"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="sid-0895E09C-077C-4D12-8C11-31F28CBC7740" id="sid-0895E09C-077C-4D12-8C11-31F28CBC7740_gui">
|
||||
<omgdi:waypoint x="580.0" y="425.0"/>
|
||||
<omgdi:waypoint x="615.0" y="425.0"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="sid-47947925-21CD-46FF-8D3F-294B235AA4CF" id="sid-47947925-21CD-46FF-8D3F-294B235AA4CF_gui">
|
||||
<omgdi:waypoint x="344.0" y="448.0"/>
|
||||
<omgdi:waypoint x="305.0" y="448.0"/>
|
||||
<omgdi:waypoint x="305.0" y="555.0"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
<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">
|
||||
<participant id="sid-B2E5AD50-035A-4CE8-B8A3-B175A6767B00" name="Parallel Looping After Join" processRef="sid-41eb2b6c-08bc-4a61-b38b-5f32052139c5">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff" />
|
||||
</extensionElements>
|
||||
</participant>
|
||||
</collaboration>
|
||||
<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">
|
||||
<lane id="sid-72009BCE-46B4-4B4B-AEAE-1E7199522142" name="Tester">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="" />
|
||||
</extensionElements>
|
||||
<flowNodeRef>start</flowNodeRef>
|
||||
<flowNodeRef>first_split</flowNodeRef>
|
||||
<flowNodeRef>one</flowNodeRef>
|
||||
<flowNodeRef>two</flowNodeRef>
|
||||
<flowNodeRef>join_of_first</flowNodeRef>
|
||||
<flowNodeRef>retry</flowNodeRef>
|
||||
<flowNodeRef>end</flowNodeRef>
|
||||
<flowNodeRef>second_split</flowNodeRef>
|
||||
<flowNodeRef>two_a</flowNodeRef>
|
||||
<flowNodeRef>two_b</flowNodeRef>
|
||||
<flowNodeRef>join_of_second_split</flowNodeRef>
|
||||
<flowNodeRef>two_done</flowNodeRef>
|
||||
<flowNodeRef>exclusive</flowNodeRef>
|
||||
<flowNodeRef>done</flowNodeRef>
|
||||
<flowNodeRef>go</flowNodeRef>
|
||||
</lane>
|
||||
</laneSet>
|
||||
<startEvent id="start" name="">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff" />
|
||||
</extensionElements>
|
||||
<outgoing>sid-F3994F51-FE54-4910-A1F4-E5895AA1A612</outgoing>
|
||||
</startEvent>
|
||||
<parallelGateway id="first_split" name="First Split" gatewayDirection="Diverging">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff" />
|
||||
</extensionElements>
|
||||
<incoming>sid-3E0EBE59-75C8-465C-90CC-197CE808A96E</incoming>
|
||||
<outgoing>sid-7E15C71B-DE9E-4788-B140-A647C99FDC94</outgoing>
|
||||
<outgoing>sid-B6E22A74-A691-453A-A789-B9F8AF787D7C</outgoing>
|
||||
</parallelGateway>
|
||||
<task id="one" name="1">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc" />
|
||||
</extensionElements>
|
||||
<incoming>sid-7E15C71B-DE9E-4788-B140-A647C99FDC94</incoming>
|
||||
<outgoing>join_of_first_split</outgoing>
|
||||
</task>
|
||||
<task id="two" name="2">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc" />
|
||||
</extensionElements>
|
||||
<incoming>sid-B6E22A74-A691-453A-A789-B9F8AF787D7C</incoming>
|
||||
<outgoing>sid-CAEAD081-6E73-4C98-8656-C67DA18F5140</outgoing>
|
||||
</task>
|
||||
<parallelGateway id="join_of_first" name="Join of First Split" gatewayDirection="Converging">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff" />
|
||||
</extensionElements>
|
||||
<incoming>sid-231F8A51-752F-4CB3-8FD1-23D153238344</incoming>
|
||||
<incoming>join_of_first_split</incoming>
|
||||
<outgoing>sid-0895E09C-077C-4D12-8C11-31F28CBC7740</outgoing>
|
||||
</parallelGateway>
|
||||
<task id="retry" name="Retry?">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc" />
|
||||
</extensionElements>
|
||||
<incoming>sid-0895E09C-077C-4D12-8C11-31F28CBC7740</incoming>
|
||||
<outgoing>sid-2668AC98-39E4-4B12-9052-930528086CAC</outgoing>
|
||||
</task>
|
||||
<endEvent id="end" name="">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff" />
|
||||
</extensionElements>
|
||||
<incoming>sid-F6160C0E-216C-4D72-98D1-CC5549327D55</incoming>
|
||||
</endEvent>
|
||||
<parallelGateway id="second_split" name="Second Split" gatewayDirection="Diverging">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff" />
|
||||
</extensionElements>
|
||||
<incoming>sid-CAEAD081-6E73-4C98-8656-C67DA18F5140</incoming>
|
||||
<outgoing>sid-918C653D-0960-4223-9C28-78114F238BCC</outgoing>
|
||||
<outgoing>sid-FD82C2A6-7C54-4890-901E-A7E864F7605C</outgoing>
|
||||
</parallelGateway>
|
||||
<task id="two_a" name="2A">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc" />
|
||||
</extensionElements>
|
||||
<incoming>sid-918C653D-0960-4223-9C28-78114F238BCC</incoming>
|
||||
<outgoing>sid-961AF51C-9935-410E-AAA4-105B19186F5E</outgoing>
|
||||
</task>
|
||||
<task id="two_b" name="2B">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc" />
|
||||
</extensionElements>
|
||||
<incoming>sid-FD82C2A6-7C54-4890-901E-A7E864F7605C</incoming>
|
||||
<outgoing>sid-47947925-21CD-46FF-8D3F-294B235AA4CF</outgoing>
|
||||
</task>
|
||||
<parallelGateway id="join_of_second_split" name="Join of Second Split" gatewayDirection="Converging">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff" />
|
||||
</extensionElements>
|
||||
<incoming>sid-961AF51C-9935-410E-AAA4-105B19186F5E</incoming>
|
||||
<incoming>sid-47947925-21CD-46FF-8D3F-294B235AA4CF</incoming>
|
||||
<outgoing>sid-AFA38469-CD5C-42A2-9473-2EAEBA61F0C0</outgoing>
|
||||
</parallelGateway>
|
||||
<task id="two_done" name="2 Done">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc" />
|
||||
</extensionElements>
|
||||
<incoming>sid-AFA38469-CD5C-42A2-9473-2EAEBA61F0C0</incoming>
|
||||
<outgoing>sid-231F8A51-752F-4CB3-8FD1-23D153238344</outgoing>
|
||||
</task>
|
||||
<exclusiveGateway id="exclusive" name="" gatewayDirection="Diverging">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffff" />
|
||||
</extensionElements>
|
||||
<incoming>sid-2668AC98-39E4-4B12-9052-930528086CAC</incoming>
|
||||
<outgoing>sid-08D6385B-C6BB-45FC-A6BD-2369F392868D</outgoing>
|
||||
<outgoing>sid-41205B5D-4DBA-4155-A0EE-7D71CE9AA459</outgoing>
|
||||
</exclusiveGateway>
|
||||
<task id="done" name="Done">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc" />
|
||||
</extensionElements>
|
||||
<incoming>sid-08D6385B-C6BB-45FC-A6BD-2369F392868D</incoming>
|
||||
<outgoing>sid-F6160C0E-216C-4D72-98D1-CC5549327D55</outgoing>
|
||||
</task>
|
||||
<task id="go" name="Go">
|
||||
<extensionElements>
|
||||
<signavio:signavioMetaData metaKey="bgcolor" metaValue="#ffffcc" />
|
||||
</extensionElements>
|
||||
<incoming>sid-41205B5D-4DBA-4155-A0EE-7D71CE9AA459</incoming>
|
||||
<incoming>sid-F3994F51-FE54-4910-A1F4-E5895AA1A612</incoming>
|
||||
<outgoing>sid-3E0EBE59-75C8-465C-90CC-197CE808A96E</outgoing>
|
||||
</task>
|
||||
<sequenceFlow id="sid-7E15C71B-DE9E-4788-B140-A647C99FDC94" name="" sourceRef="first_split" targetRef="one" />
|
||||
<sequenceFlow id="sid-B6E22A74-A691-453A-A789-B9F8AF787D7C" name="" sourceRef="first_split" targetRef="two" />
|
||||
<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="two" targetRef="second_split" />
|
||||
<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="second_split" targetRef="two_b" />
|
||||
<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="two_b" targetRef="join_of_second_split" />
|
||||
<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="two_done" targetRef="join_of_first" />
|
||||
<sequenceFlow id="join_of_first_split" name="" sourceRef="one" targetRef="join_of_first" />
|
||||
<sequenceFlow id="sid-2668AC98-39E4-4B12-9052-930528086CAC" name="" sourceRef="retry" targetRef="exclusive" />
|
||||
<sequenceFlow id="sid-08D6385B-C6BB-45FC-A6BD-2369F392868D" name="No" sourceRef="exclusive" targetRef="done" />
|
||||
<sequenceFlow id="sid-F6160C0E-216C-4D72-98D1-CC5549327D55" name="" sourceRef="done" targetRef="end" />
|
||||
<sequenceFlow id="sid-41205B5D-4DBA-4155-A0EE-7D71CE9AA459" name="Yes" sourceRef="exclusive" targetRef="go" />
|
||||
<sequenceFlow id="sid-3E0EBE59-75C8-465C-90CC-197CE808A96E" name="" sourceRef="go" targetRef="first_split" />
|
||||
<sequenceFlow id="sid-F3994F51-FE54-4910-A1F4-E5895AA1A612" name="" sourceRef="start" targetRef="go" />
|
||||
</process>
|
||||
<bpmndi:BPMNDiagram id="sid-162a2324-4820-489a-9df8-04591b6b429a">
|
||||
<bpmndi:BPMNPlane id="sid-4c9e28b7-9050-4a64-8a52-634f8f2febc3" bpmnElement="sid-5801c79c-823f-4040-b680-417ef5bcb3a2">
|
||||
<bpmndi:BPMNShape id="sid-B2E5AD50-035A-4CE8-B8A3-B175A6767B00_gui" bpmnElement="sid-B2E5AD50-035A-4CE8-B8A3-B175A6767B00" isHorizontal="true">
|
||||
<omgdc:Bounds x="120" y="90" width="794" height="619" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="sid-72009BCE-46B4-4B4B-AEAE-1E7199522142_gui" bpmnElement="sid-72009BCE-46B4-4B4B-AEAE-1E7199522142" isHorizontal="true">
|
||||
<omgdc:Bounds x="150" y="90" width="764" height="619" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="sid-F3994F51-FE54-4910-A1F4-E5895AA1A612_gui" bpmnElement="sid-F3994F51-FE54-4910-A1F4-E5895AA1A612">
|
||||
<omgdi:waypoint x="220" y="137" />
|
||||
<omgdi:waypoint x="394" y="137" />
|
||||
<omgdi:waypoint x="467" y="142" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="sid-3E0EBE59-75C8-465C-90CC-197CE808A96E_gui" bpmnElement="sid-3E0EBE59-75C8-465C-90CC-197CE808A96E">
|
||||
<omgdi:waypoint x="467" y="167" />
|
||||
<omgdi:waypoint x="414" y="191" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="sid-41205B5D-4DBA-4155-A0EE-7D71CE9AA459_gui" bpmnElement="sid-41205B5D-4DBA-4155-A0EE-7D71CE9AA459">
|
||||
<omgdi:waypoint x="794" y="405" />
|
||||
<omgdi:waypoint x="794.5" y="145" />
|
||||
<omgdi:waypoint x="567" y="145" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="sid-F6160C0E-216C-4D72-98D1-CC5549327D55_gui" bpmnElement="sid-F6160C0E-216C-4D72-98D1-CC5549327D55">
|
||||
<omgdi:waypoint x="794" y="577" />
|
||||
<omgdi:waypoint x="794" y="630" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="sid-08D6385B-C6BB-45FC-A6BD-2369F392868D_gui" bpmnElement="sid-08D6385B-C6BB-45FC-A6BD-2369F392868D">
|
||||
<omgdi:waypoint x="794" y="445" />
|
||||
<omgdi:waypoint x="794" y="497" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="sid-2668AC98-39E4-4B12-9052-930528086CAC_gui" bpmnElement="sid-2668AC98-39E4-4B12-9052-930528086CAC">
|
||||
<omgdi:waypoint x="715" y="425" />
|
||||
<omgdi:waypoint x="774" y="425" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="sid-607CB05E-8762-41B6-AD43-C3970661A99D_gui" bpmnElement="join_of_first_split">
|
||||
<omgdi:waypoint x="560" y="294" />
|
||||
<omgdi:waypoint x="560" y="405" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="sid-231F8A51-752F-4CB3-8FD1-23D153238344_gui" bpmnElement="sid-231F8A51-752F-4CB3-8FD1-23D153238344">
|
||||
<omgdi:waypoint x="475" y="535" />
|
||||
<omgdi:waypoint x="491" y="425" />
|
||||
<omgdi:waypoint x="540" y="425" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="sid-AFA38469-CD5C-42A2-9473-2EAEBA61F0C0_gui" bpmnElement="sid-AFA38469-CD5C-42A2-9473-2EAEBA61F0C0">
|
||||
<omgdi:waypoint x="325" y="575" />
|
||||
<omgdi:waypoint x="420" y="575" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="sid-47947925-21CD-46FF-8D3F-294B235AA4CF_gui" bpmnElement="sid-47947925-21CD-46FF-8D3F-294B235AA4CF">
|
||||
<omgdi:waypoint x="344" y="448" />
|
||||
<omgdi:waypoint x="305" y="448" />
|
||||
<omgdi:waypoint x="305" y="555" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="sid-961AF51C-9935-410E-AAA4-105B19186F5E_gui" bpmnElement="sid-961AF51C-9935-410E-AAA4-105B19186F5E">
|
||||
<omgdi:waypoint x="277" y="457" />
|
||||
<omgdi:waypoint x="305" y="457" />
|
||||
<omgdi:waypoint x="305" y="555" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="sid-FD82C2A6-7C54-4890-901E-A7E864F7605C_gui" bpmnElement="sid-FD82C2A6-7C54-4890-901E-A7E864F7605C">
|
||||
<omgdi:waypoint x="301" y="358" />
|
||||
<omgdi:waypoint x="394" y="358.5" />
|
||||
<omgdi:waypoint x="394" y="408" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="sid-918C653D-0960-4223-9C28-78114F238BCC_gui" bpmnElement="sid-918C653D-0960-4223-9C28-78114F238BCC">
|
||||
<omgdi:waypoint x="261" y="358" />
|
||||
<omgdi:waypoint x="227" y="358.5" />
|
||||
<omgdi:waypoint x="227" y="417" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="sid-CAEAD081-6E73-4C98-8656-C67DA18F5140_gui" bpmnElement="sid-CAEAD081-6E73-4C98-8656-C67DA18F5140">
|
||||
<omgdi:waypoint x="281" y="294" />
|
||||
<omgdi:waypoint x="281" y="338" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="sid-0895E09C-077C-4D12-8C11-31F28CBC7740_gui" bpmnElement="sid-0895E09C-077C-4D12-8C11-31F28CBC7740">
|
||||
<omgdi:waypoint x="580" y="425" />
|
||||
<omgdi:waypoint x="615" y="425" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="sid-B6E22A74-A691-453A-A789-B9F8AF787D7C_gui" bpmnElement="sid-B6E22A74-A691-453A-A789-B9F8AF787D7C">
|
||||
<omgdi:waypoint x="394" y="220" />
|
||||
<omgdi:waypoint x="394.5" y="254" />
|
||||
<omgdi:waypoint x="331" y="254" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="sid-7E15C71B-DE9E-4788-B140-A647C99FDC94_gui" bpmnElement="sid-7E15C71B-DE9E-4788-B140-A647C99FDC94">
|
||||
<omgdi:waypoint x="414" y="206" />
|
||||
<omgdi:waypoint x="510" y="238" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="sid-B33EE043-AB93-4343-A1D4-7B267E2DAFBE_gui" bpmnElement="start">
|
||||
<omgdc:Bounds x="190" y="122" width="30" height="30" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="sid-349F8C0C-45EA-489C-84DD-1D944F48D778_gui" bpmnElement="first_split">
|
||||
<omgdc:Bounds x="374" y="180" width="40" height="40" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds x="371" y="220" width="46" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="sid-57463471-693A-42A2-9EC6-6460BEDECA86_gui" bpmnElement="one">
|
||||
<omgdc:Bounds x="510" y="214" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="sid-CA089240-802A-4C32-9130-FB1A33DDCCC3_gui" bpmnElement="two">
|
||||
<omgdc:Bounds x="231" y="214" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="sid-E976FBC2-266E-420F-8D4D-C8FBC6199090_gui" bpmnElement="join_of_first">
|
||||
<omgdc:Bounds x="540" y="405" width="40" height="40" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds x="519" y="445" width="82" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="sid-F3A979E3-F586-4807-8223-1FAB5A5647B0_gui" bpmnElement="retry">
|
||||
<omgdc:Bounds x="615" y="385" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="sid-51816945-79BF-47F9-BA3C-E95ABAE3D1DB_gui" bpmnElement="end">
|
||||
<omgdc:Bounds x="780" y="630" width="28" height="28" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="sid-4F7F3AA6-4E8E-458D-BBEF-E03FC0646897_gui" bpmnElement="second_split">
|
||||
<omgdc:Bounds x="261" y="338" width="40" height="40" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds x="250" y="378" width="62" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="sid-ABD788A3-CD57-4280-A22A-260B3AEEE138_gui" bpmnElement="two_a">
|
||||
<omgdc:Bounds x="177" y="417" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="sid-E7B8898A-0D14-4E98-B3D7-736B94EE3FA7_gui" bpmnElement="two_b">
|
||||
<omgdc:Bounds x="344" y="408" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="sid-A1609BD5-1E4A-47AE-8648-1DD41D1B1D58_gui" bpmnElement="join_of_second_split">
|
||||
<omgdc:Bounds x="285" y="555" width="40" height="40" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds x="269" y="595" width="73" height="27" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="sid-1946C635-7886-4687-844F-C644FA6222B8_gui" bpmnElement="two_done">
|
||||
<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>
|
||||
|
|
|
@ -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)
|
||||
self.workflow.do_engine_steps()
|
||||
self.save_restore()
|
||||
self.workflow.complete_all()
|
||||
self.workflow.run_all()
|
||||
self.assertEqual(True, self.workflow.is_completed())
|
||||
|
||||
self.assertEqual(True, 'EndEvent_specific1_noninterrupting_normal' in completed_set)
|
||||
|
@ -81,7 +81,7 @@ class CallActivityEscalationTest(BpmnWorkflowTestCase):
|
|||
task.set_data(should_escalate=False)
|
||||
self.workflow.do_engine_steps()
|
||||
self.save_restore()
|
||||
self.workflow.complete_all()
|
||||
self.workflow.run_all()
|
||||
self.assertEqual(True, self.workflow.is_completed())
|
||||
|
||||
self.assertEqual(True, 'EndEvent_specific1_noninterrupting_normal' in completed_set)
|
||||
|
@ -109,7 +109,7 @@ class CallActivityEscalationTest(BpmnWorkflowTestCase):
|
|||
track_workflow(self.spec, completed_set)
|
||||
self.workflow.do_engine_steps()
|
||||
self.save_restore()
|
||||
self.workflow.complete_all()
|
||||
self.workflow.run_all()
|
||||
self.assertEqual(True, self.workflow.is_completed())
|
||||
|
||||
self.assertEqual(True, 'EndEvent_specific1_noninterrupting_normal' in completed_set)
|
||||
|
|
|
@ -28,7 +28,7 @@ class EventBasedGatewayTest(BpmnWorkflowTestCase):
|
|||
if save_restore:
|
||||
self.save_restore()
|
||||
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.do_engine_steps()
|
||||
self.workflow.refresh_waiting_tasks()
|
||||
|
@ -41,7 +41,7 @@ class EventBasedGatewayTest(BpmnWorkflowTestCase):
|
|||
|
||||
self.workflow.do_engine_steps()
|
||||
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]
|
||||
self.workflow.catch(timer_event)
|
||||
self.workflow.refresh_waiting_tasks()
|
||||
|
|
|
@ -33,7 +33,7 @@ class MultipleEventsTest(BpmnWorkflowTestCase):
|
|||
task = self.workflow.get_tasks(TaskState.READY)[0]
|
||||
|
||||
# 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()
|
||||
task = self.workflow.get_tasks(TaskState.READY)[0]
|
||||
self.assertEqual('UserTaskOne', task.get_name())
|
||||
|
@ -52,10 +52,10 @@ class MultipleEventsTest(BpmnWorkflowTestCase):
|
|||
task = self.workflow.get_tasks(TaskState.READY)[0]
|
||||
|
||||
# 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()
|
||||
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()
|
||||
task = self.workflow.get_tasks(TaskState.READY)[0]
|
||||
self.assertEqual('UserTaskTwo', task.get_name())
|
||||
|
|
|
@ -42,6 +42,6 @@ class MultipleThrowEventStartsEventTest(BpmnWorkflowTestCase):
|
|||
self.workflow.do_engine_steps()
|
||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||
self.assertEqual(len(ready_tasks), 1)
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
self.workflow.do_engine_steps()
|
||||
self.assertEqual(self.workflow.is_completed(), True)
|
|
@ -62,7 +62,7 @@ class NITimerDurationTest(BpmnWorkflowTestCase):
|
|||
task.data['delay_reason'] = 'Just Because'
|
||||
elif task.task_spec.name == 'Activity_Work':
|
||||
task.data['work_done'] = 'Yes'
|
||||
task.complete()
|
||||
task.run()
|
||||
self.workflow.refresh_waiting_tasks()
|
||||
self.workflow.do_engine_steps()
|
||||
self.assertEqual(self.workflow.is_completed(),True)
|
|
@ -55,11 +55,11 @@ class TimerCycleTest(BpmnWorkflowTestCase):
|
|||
time.sleep(0.05)
|
||||
self.workflow.refresh_waiting_tasks()
|
||||
events = self.workflow.waiting_events()
|
||||
if loopcount == 0:
|
||||
# Wait time is 0.1s, so the first time through, there should still be a waiting event
|
||||
if loopcount < 2:
|
||||
# Wait time is 0.1s, two child tasks are created
|
||||
self.assertEqual(len(events), 1)
|
||||
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)
|
||||
|
||||
# Get coffee still ready
|
||||
|
|
|
@ -39,7 +39,7 @@ class TimerDurationTest(BpmnWorkflowTestCase):
|
|||
|
||||
# Make sure the task can still be called.
|
||||
task = self.workflow.get_ready_user_tasks()[0]
|
||||
task.complete()
|
||||
task.run()
|
||||
self.workflow.do_engine_steps()
|
||||
self.assertTrue(self.workflow.is_completed())
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ class TimerDurationTest(BpmnWorkflowTestCase):
|
|||
def actual_test(self,save_restore = False):
|
||||
self.workflow.do_engine_steps()
|
||||
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
self.workflow.do_engine_steps()
|
||||
|
||||
loopcount = 0
|
||||
|
@ -43,7 +43,7 @@ class TimerDurationTest(BpmnWorkflowTestCase):
|
|||
self.assertEqual(subworkflow.state, TaskState.CANCELLED)
|
||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||
while len(ready_tasks) > 0:
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||
self.workflow.do_engine_steps()
|
||||
self.assertTrue(self.workflow.is_completed())
|
||||
|
|
|
@ -19,11 +19,11 @@ class TransactionSubprocessTest(BpmnWorkflowTestCase):
|
|||
|
||||
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
||||
ready_tasks[0].update_data({'value': 'asdf'})
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
self.workflow.do_engine_steps()
|
||||
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
||||
ready_tasks[0].update_data({'quantity': 2})
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
self.workflow.do_engine_steps()
|
||||
self.assertIn('value', self.workflow.last_task.data)
|
||||
|
||||
|
@ -48,7 +48,7 @@ class TransactionSubprocessTest(BpmnWorkflowTestCase):
|
|||
|
||||
# If value == '', we cancel
|
||||
ready_tasks[0].update_data({'value': ''})
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
self.workflow.do_engine_steps()
|
||||
|
||||
# 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[0].update_data({'value': 'asdf'})
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
self.workflow.do_engine_steps()
|
||||
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
||||
|
||||
# If quantity == 0, we throw an error with no error code
|
||||
ready_tasks[0].update_data({'quantity': 0})
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
self.workflow.do_engine_steps()
|
||||
|
||||
# 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[0].update_data({'value': 'asdf'})
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
self.workflow.do_engine_steps()
|
||||
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
||||
|
||||
# If quantity < 0, we throw 'Error 1'
|
||||
ready_tasks[0].update_data({'quantity': -1})
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
self.workflow.do_engine_steps()
|
||||
|
||||
# The cancel boundary event should be cancelled
|
||||
|
|
|
@ -142,7 +142,7 @@ class BpmnWorkflowSerializerTest(BaseTestCase):
|
|||
def test_serialize_workflow_where_script_task_includes_function(self):
|
||||
self.workflow.do_engine_steps()
|
||||
ready_tasks = self.workflow.get_ready_user_tasks()
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
self.workflow.do_engine_steps()
|
||||
results = self.serializer.serialize_json(self.workflow)
|
||||
assert self.workflow.is_completed()
|
||||
|
@ -161,7 +161,7 @@ class BpmnWorkflowSerializerTest(BaseTestCase):
|
|||
self.assertEqual(w1.data, w2.data)
|
||||
self.assertEqual(w1.name, w2.name)
|
||||
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.assertEqual(task.data, w2_task.data)
|
||||
|
||||
|
|
|
@ -16,11 +16,12 @@ class Version_1_0_Test(BaseTestCase):
|
|||
def test_convert_subprocess(self):
|
||||
# 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')
|
||||
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
|
||||
ready_tasks = wf.get_tasks(TaskState.READY)
|
||||
self.assertEqual('Action3', ready_tasks[0].task_spec.description)
|
||||
ready_tasks[0].complete()
|
||||
ready_tasks[0].run()
|
||||
wf.do_engine_steps()
|
||||
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)
|
||||
ready_task = wf.get_ready_user_tasks()[0]
|
||||
ready_task.data['NeedClarification'] = 'Yes'
|
||||
ready_task.complete()
|
||||
ready_task.run()
|
||||
wf.do_engine_steps()
|
||||
ready_task = wf.get_ready_user_tasks()[0]
|
||||
self.assertEqual(ready_task.task_spec.name, 'Activity_A2')
|
||||
|
@ -59,3 +60,14 @@ class Version_1_1_Test(BaseTestCase):
|
|||
with self.assertRaises(VersionMigrationError) as ctx:
|
||||
wf = self.serializer.deserialize_json(open(fn).read())
|
||||
self.assertEqual(ctx.exception.message, "This workflow cannot be migrated because it contains MultiInstance Tasks")
|
||||
|
||||
def test_remove_loop_reset(self):
|
||||
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]
|
||||
self.assertEqual(current_task.task_spec.name,step[0])
|
||||
current_task.update_data(step[1])
|
||||
current_task.complete()
|
||||
current_task.run()
|
||||
self.workflow.do_engine_steps()
|
||||
self.workflow.refresh_waiting_tasks()
|
||||
if save_restore: self.save_restore()
|
||||
|
|
|
@ -52,7 +52,7 @@ class ClashingNameTest(BaseTestCase):
|
|||
firsttaskid = task.id
|
||||
self.assertEqual(step['taskname'], task.task_spec.name)
|
||||
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()
|
||||
if save_restore: self.save_restore()
|
||||
|
||||
|
@ -68,7 +68,7 @@ class ClashingNameTest(BaseTestCase):
|
|||
task = self.workflow.get_ready_user_tasks()[0]
|
||||
self.assertEqual(step['taskname'], task.task_spec.name)
|
||||
task.update_data({step['formvar']: step['answer']})
|
||||
self.workflow.complete_task_from_id(task.id)
|
||||
self.workflow.run_task_from_id(task.id)
|
||||
self.workflow.do_engine_steps()
|
||||
if save_restore: self.save_restore()
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ class DMNCustomScriptTest(BaseTestCase):
|
|||
|
||||
def complete_manual_task(self):
|
||||
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()
|
||||
|
||||
def testDmnHappy(self):
|
||||
|
|
|
@ -16,7 +16,7 @@ class DMNDictTest(BaseTestCase):
|
|||
self.workflow = BpmnWorkflow(self.spec)
|
||||
self.workflow.do_engine_steps()
|
||||
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.assertDictEqual(self.workflow.last_task.data, self.expectedResult)
|
||||
|
||||
|
@ -25,7 +25,7 @@ class DMNDictTest(BaseTestCase):
|
|||
self.workflow.do_engine_steps()
|
||||
self.save_restore()
|
||||
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.save_restore()
|
||||
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('Meaningless User Task',ready_tasks[0].task_spec.description)
|
||||
self.assertEqual(False, ready_tasks[0].data['caughtinterrupt'])
|
||||
ready_tasks[1].complete()
|
||||
ready_tasks[1].run()
|
||||
self.workflow.do_engine_steps()
|
||||
# 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
|
||||
|
@ -52,7 +52,7 @@ class ExternalMessageBoundaryTest(BaseTestCase):
|
|||
# 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')
|
||||
event = self.workflow.get_tasks_from_spec_name('Event_19detfv')[0]
|
||||
event.complete()
|
||||
event.run()
|
||||
self.assertEqual('SomethingDrastic', event.data['reset_var'])
|
||||
self.assertEqual(False, event.data['caughtinterrupt'])
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ class BusinessRuleTaskParserTest(BaseTestCase):
|
|||
self.assertTrue(True, "An error was raised..")
|
||||
self.assertEqual("InvalidDecisionTaskId", we.task_spec.name)
|
||||
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():
|
||||
return unittest.TestLoader().loadTestsFromTestCase(BusinessRuleTaskParserTest)
|
||||
|
|
|
@ -41,7 +41,7 @@ class MessageBoundaryTest(BaseTestCase):
|
|||
if task.task_spec.name == step[0]:
|
||||
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()
|
||||
time.sleep(.01)
|
||||
self.workflow.refresh_waiting_tasks()
|
||||
|
|
|
@ -23,10 +23,10 @@ class MultiInstanceDMNTest(BaseTestCase):
|
|||
|
||||
self.save_restore()
|
||||
self.workflow.do_engine_steps()
|
||||
self.workflow.complete_next()
|
||||
self.workflow.run_next()
|
||||
self.save_restore()
|
||||
self.workflow.do_engine_steps()
|
||||
self.workflow.complete_next()
|
||||
self.workflow.run_next()
|
||||
self.save_restore()
|
||||
self.workflow.do_engine_steps()
|
||||
self.save_restore()
|
||||
|
|
|
@ -29,7 +29,7 @@ class NIMessageBoundaryTest(BaseTestCase):
|
|||
|
||||
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
||||
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()
|
||||
|
||||
# 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'%(
|
||||
task.task_spec.name))
|
||||
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()
|
||||
# if we have a list of tasks - that list becomes invalid
|
||||
# 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'%(
|
||||
task.task_spec.name))
|
||||
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()
|
||||
if save_restore: self.save_restore()
|
||||
|
||||
|
@ -75,14 +75,14 @@ class NIMessageBoundaryTest(BaseTestCase):
|
|||
task = ready_tasks[0]
|
||||
self.assertEqual(task.task_spec.name,'Activity_DoWork')
|
||||
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()
|
||||
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
||||
self.assertEqual(len(ready_tasks), 1)
|
||||
task = ready_tasks[0]
|
||||
self.assertEqual(task.task_spec.name, 'Activity_WorkCompleted')
|
||||
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.assertEqual(self.workflow.is_completed(),True)
|
||||
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)
|
||||
for task in ready_tasks:
|
||||
task.data['output_item'] = task.data['output_item'] * 2
|
||||
task.complete()
|
||||
task.run()
|
||||
|
||||
self.workflow.do_engine_steps()
|
||||
self.assertTrue(self.workflow.is_completed())
|
||||
|
@ -58,7 +58,7 @@ class ParseMultiInstanceTest(BaseTestCase):
|
|||
self.assertEqual(len(ready_tasks), 3)
|
||||
for task in ready_tasks:
|
||||
task.data['output_item'] = task.data['output_item'] * 2
|
||||
task.complete()
|
||||
task.run()
|
||||
|
||||
self.workflow.do_engine_steps()
|
||||
self.assertTrue(self.workflow.is_completed())
|
||||
|
@ -84,7 +84,7 @@ class ParseMultiInstanceTest(BaseTestCase):
|
|||
self.assertEqual(len(ready_tasks), 3)
|
||||
for task in ready_tasks:
|
||||
task.data['input_item'] = task.data['input_item'] * 2
|
||||
task.complete()
|
||||
task.run()
|
||||
|
||||
self.workflow.do_engine_steps()
|
||||
self.assertTrue(self.workflow.is_completed())
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue