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:
parent
122d1efbda
commit
27447e533c
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue