Skip to content

Commit

Permalink
Merge pull request #356 from sartography/feature/conditional-events
Browse files Browse the repository at this point in the history
add support for conditional events
  • Loading branch information
essweine authored Oct 17, 2023
2 parents c3a4943 + 996b18d commit f59ad47
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 25 deletions.
30 changes: 21 additions & 9 deletions SpiffWorkflow/bpmn/parser/event_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,10 @@
CorrelationProperty
)
from SpiffWorkflow.bpmn.specs.event_definitions.multiple import MultipleEventDefinition

from SpiffWorkflow.bpmn.specs.event_definitions.conditional import ConditionalEventDefinition

CANCEL_EVENT_XPATH = './/bpmn:cancelEventDefinition'
CONDITIONAL_EVENT_XPATH = './/bpmn:conditionalEventDefinition'
ERROR_EVENT_XPATH = './/bpmn:errorEventDefinition'
ESCALATION_EVENT_XPATH = './/bpmn:escalationEventDefinition'
TERMINATION_EVENT_XPATH = './/bpmn:terminateEventDefinition'
Expand Down Expand Up @@ -78,6 +79,12 @@ def get_event_description(self, event):
def parse_cancel_event(self, event):
return CancelEventDefinition(description=self.get_event_description(event))

def parse_conditional_event(self, event):
expression = self.xpath('.//bpmn:condition')
if len(expression) == 0:
raise ValidationException('Conditional event definition with missing condition', node=self.node, file_name=self.filename)
return ConditionalEventDefinition(expression[0].text, description=self.get_event_description(event))

def parse_error_event(self, error_event):
"""Parse the errorEventDefinition node and return an instance of ErrorEventDefinition."""
error_ref = error_event.get('errorRef')
Expand Down Expand Up @@ -202,6 +209,8 @@ def get_event_definition(self, xpaths):
event_definitions.append(self.parse_escalation_event(event))
elif path == TERMINATION_EVENT_XPATH:
event_definitions.append(self.parse_terminate_event(event))
elif path == CONDITIONAL_EVENT_XPATH:
event_definitions.append(self.parse_conditional_event(event))

parallel = self.node.get('parallelMultiple') == 'true'

Expand All @@ -218,7 +227,8 @@ class StartEventParser(EventDefinitionParser):
Support Message, Signal, and Timer events."""

def create_task(self):
event_definition = self.get_event_definition([MESSAGE_EVENT_XPATH, SIGNAL_EVENT_XPATH, TIMER_EVENT_XPATH])
event_definition = self.get_event_definition(
[MESSAGE_EVENT_XPATH, SIGNAL_EVENT_XPATH, TIMER_EVENT_XPATH, CONDITIONAL_EVENT_XPATH])
task = self._create_task(event_definition)
self.spec.start.connect(task)
return task
Expand All @@ -231,8 +241,8 @@ class EndEventParser(EventDefinitionParser):
"""Parses an End Event. Handles Termination, Escalation, Cancel, and Error End Events."""

def create_task(self):
event_definition = self.get_event_definition([MESSAGE_EVENT_XPATH, CANCEL_EVENT_XPATH, ERROR_EVENT_XPATH,
ESCALATION_EVENT_XPATH, TERMINATION_EVENT_XPATH])
event_definition = self.get_event_definition(
[MESSAGE_EVENT_XPATH, CANCEL_EVENT_XPATH, ERROR_EVENT_XPATH, ESCALATION_EVENT_XPATH, TERMINATION_EVENT_XPATH])
task = self._create_task(event_definition)
task.connect(self.spec.end)
return task
Expand All @@ -242,16 +252,17 @@ class IntermediateCatchEventParser(EventDefinitionParser):
"""Parses an Intermediate Catch Event. Currently supports Message, Signal, and Timer definitions."""

def create_task(self):
event_definition = self.get_event_definition([MESSAGE_EVENT_XPATH, SIGNAL_EVENT_XPATH, TIMER_EVENT_XPATH])
event_definition = self.get_event_definition(
[MESSAGE_EVENT_XPATH, SIGNAL_EVENT_XPATH, TIMER_EVENT_XPATH, CONDITIONAL_EVENT_XPATH])
return super()._create_task(event_definition)


class IntermediateThrowEventParser(EventDefinitionParser):
"""Parses an Intermediate Catch Event. Currently supports Message, Signal and Timer event definitions."""

def create_task(self):
event_definition = self.get_event_definition([ESCALATION_EVENT_XPATH, MESSAGE_EVENT_XPATH,
SIGNAL_EVENT_XPATH, TIMER_EVENT_XPATH])
event_definition = self.get_event_definition(
[ESCALATION_EVENT_XPATH, MESSAGE_EVENT_XPATH, SIGNAL_EVENT_XPATH, TIMER_EVENT_XPATH])
return self._create_task(event_definition)


Expand Down Expand Up @@ -284,8 +295,9 @@ class BoundaryEventParser(EventDefinitionParser):

def create_task(self):
cancel_activity = self.node.get('cancelActivity', default='true').lower() == 'true'
event_definition = self.get_event_definition([CANCEL_EVENT_XPATH, ERROR_EVENT_XPATH, ESCALATION_EVENT_XPATH,
MESSAGE_EVENT_XPATH, SIGNAL_EVENT_XPATH, TIMER_EVENT_XPATH])
event_definition = self.get_event_definition(
[CANCEL_EVENT_XPATH, ERROR_EVENT_XPATH, ESCALATION_EVENT_XPATH,
MESSAGE_EVENT_XPATH, SIGNAL_EVENT_XPATH, TIMER_EVENT_XPATH, CONDITIONAL_EVENT_XPATH])
if isinstance(event_definition, NoneEventDefinition):
raise NotImplementedError('Unsupported Catch Event: %r', etree.tostring(self.node))
return self._create_task(event_definition, cancel_activity)
Expand Down
1 change: 1 addition & 0 deletions SpiffWorkflow/bpmn/parser/spec_description.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,5 @@
full_tag('messageEventDefinition'): 'Message',
full_tag('signalEventDefinition'): 'Signal',
full_tag('timerEventDefinition'): 'Timer',
full_tag('conditionalEventDefinition'): 'Conditional',
}
12 changes: 7 additions & 5 deletions SpiffWorkflow/bpmn/serializer/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
)
from SpiffWorkflow.bpmn.specs.event_definitions.message import MessageEventDefinition
from SpiffWorkflow.bpmn.specs.event_definitions.multiple import MultipleEventDefinition
from SpiffWorkflow.bpmn.specs.event_definitions.conditional import ConditionalEventDefinition

from .default.workflow import (
BpmnWorkflowConverter,
Expand All @@ -101,7 +102,7 @@
IOSpecificationConverter,
)
from .default.event_definition import (
TimerEventDefinitionConverter,
TimerConditionalEventDefinitionConverter,
ErrorEscalationEventDefinitionConverter,
MessageEventDefinitionConverter,
MultipleEventDefinitionConverter,
Expand Down Expand Up @@ -150,8 +151,9 @@
NoneEventDefinition: EventDefinitionConverter,
SignalEventDefinition: EventDefinitionConverter,
TerminateEventDefinition: EventDefinitionConverter,
TimeDateEventDefinition: TimerEventDefinitionConverter,
DurationTimerEventDefinition: TimerEventDefinitionConverter,
CycleTimerEventDefinition: TimerEventDefinitionConverter,
TimeDateEventDefinition: TimerConditionalEventDefinitionConverter,
DurationTimerEventDefinition: TimerConditionalEventDefinitionConverter,
CycleTimerEventDefinition: TimerConditionalEventDefinitionConverter,
ConditionalEventDefinition: TimerConditionalEventDefinitionConverter,
MultipleEventDefinition: MultipleEventDefinitionConverter,
}
}
15 changes: 7 additions & 8 deletions SpiffWorkflow/bpmn/serializer/default/event_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@

from ..helpers.spec import EventDefinitionConverter

class TimerConditionalEventDefinitionConverter(EventDefinitionConverter):

def to_dict(self, event_definition):
dct = super().to_dict(event_definition)
dct['expression'] = event_definition.expression
return dct


class ErrorEscalationEventDefinitionConverter(EventDefinitionConverter):

Expand All @@ -41,14 +48,6 @@ def from_dict(self, dct):
return event_definition


class TimerEventDefinitionConverter(EventDefinitionConverter):

def to_dict(self, event_definition):
dct = super().to_dict(event_definition)
dct['expression'] = event_definition.expression
return dct


class MultipleEventDefinitionConverter(EventDefinitionConverter):

def to_dict(self, event_definition):
Expand Down
4 changes: 1 addition & 3 deletions SpiffWorkflow/bpmn/specs/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,10 @@ def _predict_hook(self, my_task):
if not isinstance(child.task_spec, BoundaryEvent):
child._set_state(state)

def _update_hook(self, my_task):
super()._update_hook(my_task)
def _run_hook(self, my_task):
for task in my_task.children:
if isinstance(task.task_spec, BoundaryEvent) and task.has_state(TaskState.PREDICTED_MASK):
task._set_state(TaskState.WAITING)
task.task_spec._predict(task)
return True


Expand Down
14 changes: 14 additions & 0 deletions SpiffWorkflow/bpmn/specs/event_definitions/conditional.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from .base import EventDefinition

class ConditionalEventDefinition(EventDefinition):
"""Conditional events can be used to trigger flows based on the state of the workflow"""

def __init__(self, expression, **kwargs):
super().__init__(**kwargs)
self.expression = expression

def has_fired(self, my_task):
my_task._set_internal_data(
has_fired=my_task.workflow.script_engine.evaluate(my_task, self.expression, external_methods=my_task.workflow.data)
)
return my_task._get_internal_data('has_fired', False)
Loading

0 comments on commit f59ad47

Please sign in to comment.