Optimistically skip some timers (#232)

This commit is contained in:
jbirddog 2023-05-01 10:32:35 -04:00 committed by GitHub
parent e9827d0d5e
commit ed3c0d7766
2 changed files with 63 additions and 1 deletions

View File

@ -2,7 +2,10 @@
import base64 import base64
import hashlib import hashlib
import time import time
from datetime import datetime
from datetime import timezone
from typing import Any from typing import Any
from typing import Dict
from typing import Generator from typing import Generator
from typing import List from typing import List
from typing import Optional from typing import Optional
@ -12,6 +15,7 @@ from urllib.parse import unquote
import sentry_sdk import sentry_sdk
from flask import current_app from flask import current_app
from flask import g from flask import g
from SpiffWorkflow.bpmn.specs.events.event_definitions import TimerEventDefinition # type: ignore
from SpiffWorkflow.bpmn.specs.events.IntermediateEvent import _BoundaryEventParent # type: ignore from SpiffWorkflow.bpmn.specs.events.IntermediateEvent import _BoundaryEventParent # type: ignore
from SpiffWorkflow.task import Task as SpiffTask # type: ignore from SpiffWorkflow.task import Task as SpiffTask # type: ignore
@ -86,6 +90,29 @@ class ProcessInstanceService:
process_model = ProcessModelService.get_process_model(process_model_identifier) process_model = ProcessModelService.get_process_model(process_model_identifier)
return cls.create_process_instance(process_model, user) return cls.create_process_instance(process_model, user)
@classmethod
def waiting_event_can_be_skipped(cls, waiting_event: Dict[str, Any], now_in_utc: datetime) -> bool:
#
# over time this function can gain more knowledge of different event types,
# for now we are just handling Duration Timer events.
#
# example: {'event_type': 'Duration Timer', 'name': None, 'value': '2023-04-27T20:15:10.626656+00:00'}
#
event_type = waiting_event.get("event_type")
if event_type == "Duration Timer":
event_value = waiting_event.get("value")
if event_value is not None:
event_datetime = TimerEventDefinition.get_datetime(event_value)
return event_datetime > now_in_utc # type: ignore
return False
@classmethod
def all_waiting_events_can_be_skipped(cls, waiting_events: List[Dict[str, Any]]) -> bool:
for waiting_event in waiting_events:
if not cls.waiting_event_can_be_skipped(waiting_event, datetime.now(timezone.utc)):
return False
return True
@classmethod @classmethod
def ready_user_task_has_associated_timer(cls, processor: ProcessInstanceProcessor) -> bool: def ready_user_task_has_associated_timer(cls, processor: ProcessInstanceProcessor) -> bool:
for ready_user_task in processor.bpmn_process_instance.get_ready_user_tasks(): for ready_user_task in processor.bpmn_process_instance.get_ready_user_tasks():
@ -101,7 +128,10 @@ class ProcessInstanceService:
if processor.process_instance_model.status != status_value: if processor.process_instance_model.status != status_value:
return True return True
return status_value == "user_input_required" and not cls.ready_user_task_has_associated_timer(processor) if status_value == "user_input_required" and cls.ready_user_task_has_associated_timer(processor):
return cls.all_waiting_events_can_be_skipped(processor.bpmn_process_instance.waiting_events())
return False
@classmethod @classmethod
def do_waiting(cls, status_value: str = ProcessInstanceStatus.waiting.value) -> None: def do_waiting(cls, status_value: str = ProcessInstanceStatus.waiting.value) -> None:

View File

@ -1,4 +1,6 @@
"""Test_process_instance_processor.""" """Test_process_instance_processor."""
from datetime import datetime
from datetime import timezone
from typing import Optional from typing import Optional
from flask.app import Flask from flask.app import Flask
@ -213,3 +215,33 @@ class TestProcessInstanceService(BaseTest):
assert len(models) == 2 assert len(models) == 2
self._check_sample_file_data_model("File", 0, models[0]) self._check_sample_file_data_model("File", 0, models[0])
self._check_sample_file_data_model("File", 1, models[1]) self._check_sample_file_data_model("File", 1, models[1])
def test_does_not_skip_events_it_does_not_know_about(self) -> None:
assert not (
ProcessInstanceService.waiting_event_can_be_skipped(
{"event_type": "Unknown", "name": None, "value": "2023-04-27T20:15:10.626656+00:00"},
datetime.now(timezone.utc),
)
)
def test_does_skip_duration_timer_events_for_the_future(self) -> None:
assert ProcessInstanceService.waiting_event_can_be_skipped(
{"event_type": "Duration Timer", "name": None, "value": "2023-04-27T20:15:10.626656+00:00"},
datetime.fromisoformat("2023-04-26T20:15:10.626656+00:00"),
)
def test_does_not_skip_duration_timer_events_for_the_past(self) -> None:
assert not (
ProcessInstanceService.waiting_event_can_be_skipped(
{"event_type": "Duration Timer", "name": None, "value": "2023-04-27T20:15:10.626656+00:00"},
datetime.fromisoformat("2023-04-28T20:15:10.626656+00:00"),
)
)
def test_does_not_skip_duration_timer_events_for_now(self) -> None:
assert not (
ProcessInstanceService.waiting_event_can_be_skipped(
{"event_type": "Duration Timer", "name": None, "value": "2023-04-27T20:15:10.626656+00:00"},
datetime.fromisoformat("2023-04-27T20:15:10.626656+00:00"),
)
)