Fix issue when timer start event is greater than a day (#303)

* Fix issue when start time is greater than a day

* Add some cycle timer tests

* Add some more tests

* Getting ./bin/pyl to pass
This commit is contained in:
jbirddog 2023-06-06 21:25:26 -04:00 committed by GitHub
parent 122d1efbda
commit 27447e533c
2 changed files with 171 additions and 4 deletions

View File

@ -1,4 +1,5 @@
from datetime import datetime from datetime import datetime
from datetime import timedelta
from typing import Any from typing import Any
from SpiffWorkflow.bpmn.parser.util import full_tag # type: ignore from SpiffWorkflow.bpmn.parser.util import full_tag # type: ignore
@ -39,24 +40,23 @@ class StartEvent(DefaultStartEvent): # type: ignore
def configuration(self, my_task: SpiffTask, now_in_utc: datetime) -> StartConfiguration: def configuration(self, my_task: SpiffTask, now_in_utc: datetime) -> StartConfiguration:
evaluated_expression = self.evaluated_timer_expression(my_task) evaluated_expression = self.evaluated_timer_expression(my_task)
cycles = 0 cycles = 0
start_delay_in_seconds = 0
duration = 0 duration = 0
time_delta = timedelta(seconds=0)
if evaluated_expression is not None: if evaluated_expression is not None:
if isinstance(self.timer_definition, TimeDateEventDefinition): if isinstance(self.timer_definition, TimeDateEventDefinition):
parsed_duration = TimerEventDefinition.parse_time_or_duration(evaluated_expression) parsed_duration = TimerEventDefinition.parse_time_or_duration(evaluated_expression)
time_delta = parsed_duration - now_in_utc time_delta = parsed_duration - now_in_utc
start_delay_in_seconds = time_delta.seconds
elif isinstance(self.timer_definition, DurationTimerEventDefinition): elif isinstance(self.timer_definition, DurationTimerEventDefinition):
parsed_duration = TimerEventDefinition.parse_iso_duration(evaluated_expression) parsed_duration = TimerEventDefinition.parse_iso_duration(evaluated_expression)
time_delta = TimerEventDefinition.get_timedelta_from_start(parsed_duration, now_in_utc) time_delta = TimerEventDefinition.get_timedelta_from_start(parsed_duration, now_in_utc)
start_delay_in_seconds = time_delta.seconds
elif isinstance(self.timer_definition, CycleTimerEventDefinition): elif isinstance(self.timer_definition, CycleTimerEventDefinition):
cycles, start, cycle_duration = TimerEventDefinition.parse_iso_recurring_interval(evaluated_expression) cycles, start, cycle_duration = TimerEventDefinition.parse_iso_recurring_interval(evaluated_expression)
time_delta = start - now_in_utc + cycle_duration time_delta = start - now_in_utc + cycle_duration
start_delay_in_seconds = time_delta.seconds
duration = cycle_duration.seconds duration = cycle_duration.seconds
start_delay_in_seconds = int(time_delta.total_seconds())
return (cycles, start_delay_in_seconds, duration) return (cycles, start_delay_in_seconds, duration)
def evaluated_timer_expression(self, my_task: SpiffTask) -> Any: def evaluated_timer_expression(self, my_task: SpiffTask) -> Any:

View File

@ -46,6 +46,14 @@ def example_start_datetime_minus_5_mins_in_utc(
yield example_datetime - timedelta(minutes=5) yield example_datetime - timedelta(minutes=5)
@pytest.fixture()
def example_start_datetime_minus_1_day_and_5_mins_in_utc(
example_start_datetime_in_utc_str: str,
) -> Generator[datetime, None, None]:
example_datetime = datetime.fromisoformat(example_start_datetime_in_utc_str)
yield example_datetime - timedelta(days=1, minutes=5)
class CustomBpmnDmnParser(BpmnDmnParser): # type: ignore class CustomBpmnDmnParser(BpmnDmnParser): # type: ignore
OVERRIDE_PARSER_CLASSES = {} OVERRIDE_PARSER_CLASSES = {}
OVERRIDE_PARSER_CLASSES.update(BpmnDmnParser.OVERRIDE_PARSER_CLASSES) OVERRIDE_PARSER_CLASSES.update(BpmnDmnParser.OVERRIDE_PARSER_CLASSES)
@ -85,6 +93,25 @@ class TestWorkflowService(BaseTest):
_, delay, _ = WorkflowService.next_start_event_configuration(workflow, now_in_utc) # type: ignore _, delay, _ = WorkflowService.next_start_event_configuration(workflow, now_in_utc) # type: ignore
assert delay == 0 assert delay == 0
def regular_start_events_have_no_cycles(self, now_in_utc: datetime) -> None:
workflow = workflow_from_fragment(
"""
<bpmn:process id="no_tasks" name="No Tasks" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_184umot</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:endEvent id="Event_0qq9il3">
<bpmn:incoming>Flow_184umot</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_184umot" sourceRef="StartEvent_1" targetRef="Event_0qq9il3" />
</bpmn:process>
""",
"no_tasks",
)
cycles, _, duration = WorkflowService.next_start_event_configuration(workflow, now_in_utc) # type: ignore
assert cycles == 0
assert duration == 0
def test_run_at_delay_is_30_for_30_second_duration_start_timer_event(self, now_in_utc: datetime) -> None: def test_run_at_delay_is_30_for_30_second_duration_start_timer_event(self, now_in_utc: datetime) -> None:
workflow = workflow_from_fragment( workflow = workflow_from_fragment(
""" """
@ -106,6 +133,49 @@ class TestWorkflowService(BaseTest):
_, delay, _ = WorkflowService.next_start_event_configuration(workflow, now_in_utc) # type: ignore _, delay, _ = WorkflowService.next_start_event_configuration(workflow, now_in_utc) # type: ignore
assert delay == 30 assert delay == 30
def test_run_at_delay_is_10000_for_10000_second_duration_start_timer_event(self, now_in_utc: datetime) -> None:
workflow = workflow_from_fragment(
"""
<bpmn:process id="Process_aldvgey" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1x1o335</bpmn:outgoing>
<bpmn:timerEventDefinition id="TimerEventDefinition_1vi6a54">
<bpmn:timeDuration xsi:type="bpmn:tFormalExpression">"PT10000S"</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1x1o335" sourceRef="StartEvent_1" targetRef="Event_0upbokh" />
<bpmn:endEvent id="Event_0upbokh">
<bpmn:incoming>Flow_1x1o335</bpmn:incoming>
</bpmn:endEvent>
</bpmn:process>
""",
"Process_aldvgey",
)
_, delay, _ = WorkflowService.next_start_event_configuration(workflow, now_in_utc) # type: ignore
assert delay == 10000
def test_duration_start_timer_event_have_no_cylces(self, now_in_utc: datetime) -> None:
workflow = workflow_from_fragment(
"""
<bpmn:process id="Process_aldvgey" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1x1o335</bpmn:outgoing>
<bpmn:timerEventDefinition id="TimerEventDefinition_1vi6a54">
<bpmn:timeDuration xsi:type="bpmn:tFormalExpression">"PT30S"</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1x1o335" sourceRef="StartEvent_1" targetRef="Event_0upbokh" />
<bpmn:endEvent id="Event_0upbokh">
<bpmn:incoming>Flow_1x1o335</bpmn:incoming>
</bpmn:endEvent>
</bpmn:process>
""",
"Process_aldvgey",
)
cycles, _, duration = WorkflowService.next_start_event_configuration(workflow, now_in_utc) # type: ignore
assert cycles == 0
assert duration == 0
def test_run_at_delay_is_300_if_5_mins_before_date_start_timer_event( def test_run_at_delay_is_300_if_5_mins_before_date_start_timer_event(
self, example_start_datetime_in_utc_str: str, example_start_datetime_minus_5_mins_in_utc: datetime self, example_start_datetime_in_utc_str: str, example_start_datetime_minus_5_mins_in_utc: datetime
) -> None: ) -> None:
@ -130,3 +200,100 @@ class TestWorkflowService(BaseTest):
workflow, example_start_datetime_minus_5_mins_in_utc workflow, example_start_datetime_minus_5_mins_in_utc
) # type: ignore ) # type: ignore
assert delay == 300 assert delay == 300
def test_run_at_delay_is_86700_if_1_day_and_5_mins_before_date_start_timer_event(
self, example_start_datetime_in_utc_str: str, example_start_datetime_minus_1_day_and_5_mins_in_utc: datetime
) -> None:
workflow = workflow_from_fragment(
f"""
<bpmn:process id="Process_aldvgey" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1x1o335</bpmn:outgoing>
<bpmn:timerEventDefinition id="TimerEventDefinition_1vi6a54">
<bpmn:timeDate xsi:type="bpmn:tFormalExpression">"{example_start_datetime_in_utc_str}"</bpmn:timeDate>
</bpmn:timerEventDefinition>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1x1o335" sourceRef="StartEvent_1" targetRef="Event_0upbokh" />
<bpmn:endEvent id="Event_0upbokh">
<bpmn:incoming>Flow_1x1o335</bpmn:incoming>
</bpmn:endEvent>
</bpmn:process>
""",
"Process_aldvgey",
)
_, delay, _ = WorkflowService.next_start_event_configuration(
workflow, example_start_datetime_minus_1_day_and_5_mins_in_utc
) # type: ignore
assert delay == 86700
def date_start_timer_event_has_no_cycles(
self, example_start_datetime_in_utc_str: str, example_start_datetime_minus_1_day_and_5_mins_in_utc: datetime
) -> None:
workflow = workflow_from_fragment(
f"""
<bpmn:process id="Process_aldvgey" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1x1o335</bpmn:outgoing>
<bpmn:timerEventDefinition id="TimerEventDefinition_1vi6a54">
<bpmn:timeDate xsi:type="bpmn:tFormalExpression">"{example_start_datetime_in_utc_str}"</bpmn:timeDate>
</bpmn:timerEventDefinition>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1x1o335" sourceRef="StartEvent_1" targetRef="Event_0upbokh" />
<bpmn:endEvent id="Event_0upbokh">
<bpmn:incoming>Flow_1x1o335</bpmn:incoming>
</bpmn:endEvent>
</bpmn:process>
""",
"Process_aldvgey",
)
cycles, _, duration = WorkflowService.next_start_event_configuration(
workflow, example_start_datetime_minus_1_day_and_5_mins_in_utc
) # type: ignore
assert cycles == 0
assert duration == 0
def test_5_cycles_of_30_second_cycle_start_timer_event(self, now_in_utc: datetime) -> None:
workflow = workflow_from_fragment(
"""
<bpmn:process id="Process_aldvgey" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1x1o335</bpmn:outgoing>
<bpmn:timerEventDefinition id="TimerEventDefinition_1vi6a54">
<bpmn:timeCycle xsi:type="bpmn:tFormalExpression">"R5/PT30S"</bpmn:timeCycle>
</bpmn:timerEventDefinition>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1x1o335" sourceRef="StartEvent_1" targetRef="Event_0upbokh" />
<bpmn:endEvent id="Event_0upbokh">
<bpmn:incoming>Flow_1x1o335</bpmn:incoming>
</bpmn:endEvent>
</bpmn:process>
""",
"Process_aldvgey",
)
cycles, delay, duration = WorkflowService.next_start_event_configuration(workflow, now_in_utc) # type: ignore
assert cycles == 5
assert delay == 30
assert duration == 30
def test_5_cycles_of_10000_second_cycle_start_timer_event(self, now_in_utc: datetime) -> None:
workflow = workflow_from_fragment(
"""
<bpmn:process id="Process_aldvgey" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1x1o335</bpmn:outgoing>
<bpmn:timerEventDefinition id="TimerEventDefinition_1vi6a54">
<bpmn:timeCycle xsi:type="bpmn:tFormalExpression">"R5/PT10000S"</bpmn:timeCycle>
</bpmn:timerEventDefinition>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1x1o335" sourceRef="StartEvent_1" targetRef="Event_0upbokh" />
<bpmn:endEvent id="Event_0upbokh">
<bpmn:incoming>Flow_1x1o335</bpmn:incoming>
</bpmn:endEvent>
</bpmn:process>
""",
"Process_aldvgey",
)
cycles, delay, duration = WorkflowService.next_start_event_configuration(workflow, now_in_utc) # type: ignore
assert cycles == 5
assert delay == 10000
assert duration == 10000