Squashed 'SpiffWorkflow/' changes from ffb16867..841bd630
841bd630 Merge pull request #273 from sartography/bugfix/catch-timers-in-event-gateways 103c70d0 hacks to handle timer events like regular events git-subtree-dir: SpiffWorkflow git-subtree-split: 841bd63017bb1d92858456393f144b4e5b23c994
This commit is contained in:
parent
71a2a3ec0e
commit
df6e065606
|
@ -81,17 +81,18 @@ class EventDefinitionParser(TaskParser):
|
|||
"""Parse the timerEventDefinition node and return an instance of TimerEventDefinition."""
|
||||
|
||||
try:
|
||||
label = self.node.get('name', self.node.get('id'))
|
||||
time_date = first(self.xpath('.//bpmn:timeDate'))
|
||||
if time_date is not None:
|
||||
return TimerEventDefinition(self.node.get('name'), time_date.text)
|
||||
return TimerEventDefinition(label, time_date.text)
|
||||
|
||||
time_duration = first(self.xpath('.//bpmn:timeDuration'))
|
||||
if time_duration is not None:
|
||||
return TimerEventDefinition(self.node.get('name'), time_duration.text)
|
||||
return TimerEventDefinition(label, time_duration.text)
|
||||
|
||||
time_cycle = first(self.xpath('.//bpmn:timeCycle'))
|
||||
if time_cycle is not None:
|
||||
return CycleTimerEventDefinition(self.node.get('name'), time_cycle.text)
|
||||
return CycleTimerEventDefinition(label, time_cycle.text)
|
||||
raise ValidationException("Unknown Time Specification", node=self.node, filename=self.filename)
|
||||
except Exception as e:
|
||||
raise ValidationException("Time Specification Error. " + str(e), node=self.node, filename=self.filename)
|
||||
|
|
|
@ -156,6 +156,9 @@ class EventBasedGateway(CatchingEvent):
|
|||
def spec_type(self):
|
||||
return 'Event Based Gateway'
|
||||
|
||||
def _predict_hook(self, my_task):
|
||||
my_task._sync_children(self.outputs, state=TaskState.MAYBE)
|
||||
|
||||
def _on_complete_hook(self, my_task):
|
||||
for child in my_task.children:
|
||||
if not child.task_spec.event_definition.has_fired(child):
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
import datetime
|
||||
from copy import deepcopy
|
||||
|
||||
from SpiffWorkflow.task import TaskState
|
||||
|
||||
class EventDefinition(object):
|
||||
"""
|
||||
|
@ -307,6 +308,11 @@ class TimerEventDefinition(EventDefinition):
|
|||
The Timer is considered to have fired if the evaluated dateTime
|
||||
expression is before datetime.datetime.now()
|
||||
"""
|
||||
|
||||
if my_task.internal_data.get('event_fired'):
|
||||
# If we manually send this event, this will be set
|
||||
return True
|
||||
|
||||
dt = my_task.workflow.script_engine.evaluate(my_task, self.dateTime)
|
||||
if isinstance(dt,datetime.timedelta):
|
||||
if my_task._get_internal_data('start_time',None) is not None:
|
||||
|
@ -330,6 +336,9 @@ class TimerEventDefinition(EventDefinition):
|
|||
now = datetime.date.today()
|
||||
return now > dt
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__class__.__name__ == other.__class__.__name__ and self.label == other.label
|
||||
|
||||
def serialize(self):
|
||||
retdict = super(TimerEventDefinition, self).serialize()
|
||||
retdict['label'] = self.label
|
||||
|
@ -363,6 +372,10 @@ class CycleTimerEventDefinition(EventDefinition):
|
|||
# We will fire this timer whenever a cycle completes
|
||||
# The task itself will manage counting how many times it fires
|
||||
|
||||
if my_task.internal_data.get('event_fired'):
|
||||
# If we manually send this event, this will be set
|
||||
return True
|
||||
|
||||
repeat, delta = my_task.workflow.script_engine.evaluate(my_task, self.cycle_definition)
|
||||
|
||||
# This is the first time we've entered this event
|
||||
|
@ -393,6 +406,9 @@ class CycleTimerEventDefinition(EventDefinition):
|
|||
my_task.internal_data['start_time'] = None
|
||||
super(CycleTimerEventDefinition, self).reset(my_task)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__class__.__name__ == other.__class__.__name__ and self.label == other.label
|
||||
|
||||
def serialize(self):
|
||||
retdict = super(CycleTimerEventDefinition, self).serialize()
|
||||
retdict['label'] = self.label
|
||||
|
@ -411,19 +427,27 @@ class MultipleEventDefinition(EventDefinition):
|
|||
def event_type(self):
|
||||
return 'Multiple'
|
||||
|
||||
def catch(self, my_task, event_definition=None):
|
||||
event_definition.catch(my_task, event_definition)
|
||||
def has_fired(self, my_task):
|
||||
|
||||
seen_events = my_task.internal_data.get('seen_events', [])
|
||||
for event in self.event_definitions:
|
||||
if isinstance(event, (TimerEventDefinition, CycleTimerEventDefinition)):
|
||||
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):
|
||||
seen_events.append(event)
|
||||
|
||||
if self.parallel:
|
||||
# Parallel multiple need to match all events
|
||||
seen_events = my_task.internal_data.get('seen_events', []) + [event_definition]
|
||||
my_task._set_internal_data(seen_events=seen_events)
|
||||
if all(event in seen_events for event in self.event_definitions):
|
||||
my_task._set_internal_data(event_fired=True)
|
||||
else:
|
||||
my_task._set_internal_data(event_fired=False)
|
||||
return all(event in seen_events for event in self.event_definitions)
|
||||
else:
|
||||
# Otherwise, matching one is sufficient
|
||||
my_task._set_internal_data(event_fired=True)
|
||||
return len(seen_events) > 0
|
||||
|
||||
def catch(self, my_task, event_definition=None):
|
||||
event_definition.catch(my_task, event_definition)
|
||||
seen_events = my_task.internal_data.get('seen_events', []) + [event_definition]
|
||||
my_task._set_internal_data(seen_events=seen_events)
|
||||
|
||||
def reset(self, my_task):
|
||||
my_task.internal_data.pop('seen_events', None)
|
||||
|
|
|
@ -54,7 +54,7 @@ class CatchingEvent(Simple, BpmnSpecMixin):
|
|||
my_task._ready()
|
||||
super(CatchingEvent, self)._update_hook(my_task)
|
||||
|
||||
def _on_ready(self, my_task):
|
||||
def _on_ready_hook(self, my_task):
|
||||
|
||||
# None events don't propogate, so as soon as we're ready, we fire our event
|
||||
if isinstance(self.event_definition, NoneEventDefinition):
|
||||
|
@ -63,7 +63,7 @@ class CatchingEvent(Simple, BpmnSpecMixin):
|
|||
# If we have not seen the event we're waiting for, enter the waiting state
|
||||
if not self.event_definition.has_fired(my_task):
|
||||
my_task._set_state(TaskState.WAITING)
|
||||
super(CatchingEvent, self)._on_ready(my_task)
|
||||
super(CatchingEvent, self)._on_ready_hook(my_task)
|
||||
|
||||
def _on_complete_hook(self, my_task):
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ from functools import partial
|
|||
from SpiffWorkflow.bpmn.serializer.bpmn_converters import BpmnTaskSpecConverter
|
||||
from SpiffWorkflow.bpmn.specs.events.StartEvent import StartEvent
|
||||
from SpiffWorkflow.bpmn.specs.events.EndEvent import EndEvent
|
||||
from SpiffWorkflow.bpmn.specs.events.IntermediateEvent import IntermediateThrowEvent, IntermediateCatchEvent, BoundaryEvent
|
||||
from SpiffWorkflow.bpmn.specs.events.IntermediateEvent import IntermediateThrowEvent, IntermediateCatchEvent, BoundaryEvent, EventBasedGateway
|
||||
from SpiffWorkflow.spiff.specs.none_task import NoneTask
|
||||
from SpiffWorkflow.spiff.specs.manual_task import ManualTask
|
||||
from SpiffWorkflow.spiff.specs.user_task import UserTask
|
||||
|
@ -164,3 +164,7 @@ class ReceiveTaskConverter(SpiffEventConverter):
|
|||
dct['prescript'] = spec.prescript
|
||||
dct['postscript'] = spec.postscript
|
||||
return dct
|
||||
|
||||
class EventBasedGatewayConverter(SpiffEventConverter):
|
||||
def __init__(self, data_converter=None, typename=None):
|
||||
super().__init__(EventBasedGateway, data_converter, typename)
|
|
@ -19,7 +19,7 @@ class EventBsedGatewayTest(BpmnWorkflowTestCase):
|
|||
|
||||
def testEventBasedGatewaySaveRestore(self):
|
||||
self.actual_test(True)
|
||||
|
||||
|
||||
def actual_test(self, save_restore=False):
|
||||
|
||||
self.workflow.do_engine_steps()
|
||||
|
@ -36,6 +36,20 @@ class EventBsedGatewayTest(BpmnWorkflowTestCase):
|
|||
self.assertEqual(self.workflow.get_tasks_from_spec_name('message_2_event')[0].state, TaskState.CANCELLED)
|
||||
self.assertEqual(self.workflow.get_tasks_from_spec_name('timer_event')[0].state, TaskState.CANCELLED)
|
||||
|
||||
def testTimeout(self):
|
||||
|
||||
self.workflow.do_engine_steps()
|
||||
waiting_tasks = self.workflow.get_waiting_tasks()
|
||||
self.assertEqual(len(waiting_tasks), 1)
|
||||
timer_event = waiting_tasks[0].task_spec.event_definition.event_definitions[-1]
|
||||
self.workflow.catch(timer_event)
|
||||
self.workflow.refresh_waiting_tasks()
|
||||
self.workflow.do_engine_steps()
|
||||
self.assertEqual(self.workflow.is_completed(), True)
|
||||
self.assertEqual(self.workflow.get_tasks_from_spec_name('message_1_event')[0].state, TaskState.CANCELLED)
|
||||
self.assertEqual(self.workflow.get_tasks_from_spec_name('message_2_event')[0].state, TaskState.CANCELLED)
|
||||
self.assertEqual(self.workflow.get_tasks_from_spec_name('timer_event')[0].state, TaskState.COMPLETED)
|
||||
|
||||
def testMultipleStart(self):
|
||||
spec, subprocess = self.load_workflow_spec('multiple-start-parallel.bpmn', 'main')
|
||||
workflow = BpmnWorkflow(spec)
|
||||
|
|
Loading…
Reference in New Issue