Merge commit 'df6e065606b6b5b930754697f3332c4daebb4c9e'

This commit is contained in:
jasquat 2022-12-16 13:23:00 -05:00
commit 768a8ebeb6
6 changed files with 63 additions and 17 deletions

View File

@ -81,17 +81,18 @@ class EventDefinitionParser(TaskParser):
"""Parse the timerEventDefinition node and return an instance of TimerEventDefinition.""" """Parse the timerEventDefinition node and return an instance of TimerEventDefinition."""
try: try:
label = self.node.get('name', self.node.get('id'))
time_date = first(self.xpath('.//bpmn:timeDate')) time_date = first(self.xpath('.//bpmn:timeDate'))
if time_date is not None: 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')) time_duration = first(self.xpath('.//bpmn:timeDuration'))
if time_duration is not None: 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')) time_cycle = first(self.xpath('.//bpmn:timeCycle'))
if time_cycle is not None: 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) raise ValidationException("Unknown Time Specification", node=self.node, filename=self.filename)
except Exception as e: except Exception as e:
raise ValidationException("Time Specification Error. " + str(e), node=self.node, filename=self.filename) raise ValidationException("Time Specification Error. " + str(e), node=self.node, filename=self.filename)

View File

@ -156,6 +156,9 @@ class EventBasedGateway(CatchingEvent):
def spec_type(self): def spec_type(self):
return 'Event Based Gateway' 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): def _on_complete_hook(self, my_task):
for child in my_task.children: for child in my_task.children:
if not child.task_spec.event_definition.has_fired(child): if not child.task_spec.event_definition.has_fired(child):

View File

@ -20,6 +20,7 @@
import datetime import datetime
from copy import deepcopy from copy import deepcopy
from SpiffWorkflow.task import TaskState
class EventDefinition(object): class EventDefinition(object):
""" """
@ -307,6 +308,11 @@ class TimerEventDefinition(EventDefinition):
The Timer is considered to have fired if the evaluated dateTime The Timer is considered to have fired if the evaluated dateTime
expression is before datetime.datetime.now() 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) dt = my_task.workflow.script_engine.evaluate(my_task, self.dateTime)
if isinstance(dt,datetime.timedelta): if isinstance(dt,datetime.timedelta):
if my_task._get_internal_data('start_time',None) is not None: if my_task._get_internal_data('start_time',None) is not None:
@ -330,6 +336,9 @@ class TimerEventDefinition(EventDefinition):
now = datetime.date.today() now = datetime.date.today()
return now > dt return now > dt
def __eq__(self, other):
return self.__class__.__name__ == other.__class__.__name__ and self.label == other.label
def serialize(self): def serialize(self):
retdict = super(TimerEventDefinition, self).serialize() retdict = super(TimerEventDefinition, self).serialize()
retdict['label'] = self.label retdict['label'] = self.label
@ -363,6 +372,10 @@ class CycleTimerEventDefinition(EventDefinition):
# We will fire this timer whenever a cycle completes # We will fire this timer whenever a cycle completes
# The task itself will manage counting how many times it fires # 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) repeat, delta = my_task.workflow.script_engine.evaluate(my_task, self.cycle_definition)
# This is the first time we've entered this event # This is the first time we've entered this event
@ -393,6 +406,9 @@ class CycleTimerEventDefinition(EventDefinition):
my_task.internal_data['start_time'] = None my_task.internal_data['start_time'] = None
super(CycleTimerEventDefinition, self).reset(my_task) 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): def serialize(self):
retdict = super(CycleTimerEventDefinition, self).serialize() retdict = super(CycleTimerEventDefinition, self).serialize()
retdict['label'] = self.label retdict['label'] = self.label
@ -411,19 +427,27 @@ class MultipleEventDefinition(EventDefinition):
def event_type(self): def event_type(self):
return 'Multiple' return 'Multiple'
def catch(self, my_task, event_definition=None): def has_fired(self, my_task):
event_definition.catch(my_task, event_definition)
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: if self.parallel:
# Parallel multiple need to match all events # Parallel multiple need to match all events
seen_events = my_task.internal_data.get('seen_events', []) + [event_definition] return all(event in seen_events for event in self.event_definitions)
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)
else: else:
# Otherwise, matching one is sufficient return len(seen_events) > 0
my_task._set_internal_data(event_fired=True)
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): def reset(self, my_task):
my_task.internal_data.pop('seen_events', None) my_task.internal_data.pop('seen_events', None)

View File

@ -54,7 +54,7 @@ class CatchingEvent(Simple, BpmnSpecMixin):
my_task._ready() my_task._ready()
super(CatchingEvent, self)._update_hook(my_task) 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 # None events don't propogate, so as soon as we're ready, we fire our event
if isinstance(self.event_definition, NoneEventDefinition): 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 we have not seen the event we're waiting for, enter the waiting state
if not self.event_definition.has_fired(my_task): if not self.event_definition.has_fired(my_task):
my_task._set_state(TaskState.WAITING) 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): def _on_complete_hook(self, my_task):

View File

@ -3,7 +3,7 @@ from functools import partial
from SpiffWorkflow.bpmn.serializer.bpmn_converters import BpmnTaskSpecConverter from SpiffWorkflow.bpmn.serializer.bpmn_converters import BpmnTaskSpecConverter
from SpiffWorkflow.bpmn.specs.events.StartEvent import StartEvent from SpiffWorkflow.bpmn.specs.events.StartEvent import StartEvent
from SpiffWorkflow.bpmn.specs.events.EndEvent import EndEvent 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.none_task import NoneTask
from SpiffWorkflow.spiff.specs.manual_task import ManualTask from SpiffWorkflow.spiff.specs.manual_task import ManualTask
from SpiffWorkflow.spiff.specs.user_task import UserTask from SpiffWorkflow.spiff.specs.user_task import UserTask
@ -164,3 +164,7 @@ class ReceiveTaskConverter(SpiffEventConverter):
dct['prescript'] = spec.prescript dct['prescript'] = spec.prescript
dct['postscript'] = spec.postscript dct['postscript'] = spec.postscript
return dct return dct
class EventBasedGatewayConverter(SpiffEventConverter):
def __init__(self, data_converter=None, typename=None):
super().__init__(EventBasedGateway, data_converter, typename)

View File

@ -19,7 +19,7 @@ class EventBsedGatewayTest(BpmnWorkflowTestCase):
def testEventBasedGatewaySaveRestore(self): def testEventBasedGatewaySaveRestore(self):
self.actual_test(True) self.actual_test(True)
def actual_test(self, save_restore=False): def actual_test(self, save_restore=False):
self.workflow.do_engine_steps() 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('message_2_event')[0].state, TaskState.CANCELLED)
self.assertEqual(self.workflow.get_tasks_from_spec_name('timer_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): def testMultipleStart(self):
spec, subprocess = self.load_workflow_spec('multiple-start-parallel.bpmn', 'main') spec, subprocess = self.load_workflow_spec('multiple-start-parallel.bpmn', 'main')
workflow = BpmnWorkflow(spec) workflow = BpmnWorkflow(spec)