mirror of
https://github.com/sartography/spiff-arena.git
synced 2025-03-01 01:20:45 +00:00
Omit completed subprocesses (#1327)
* updates to avoid loading completed subprocesses when loading a processor w/ burnettk * bpmn unit test generator in controller test is passing w/ burnettk * most tests are passing again w/ burnettk * tests are all passing now w/ burnettk * added assertion to reset pi test to ensure task data does come back w/ burnettk * removed debug benchmark stuff w/ burnettk * pyl w/ burnettk --------- Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
parent
d7bd03bb69
commit
b35ab336f5
2
spiffworkflow-backend/poetry.lock
generated
2
spiffworkflow-backend/poetry.lock
generated
@ -3031,7 +3031,7 @@ doc = ["sphinx", "sphinx_rtd_theme"]
|
|||||||
type = "git"
|
type = "git"
|
||||||
url = "https://github.com/sartography/SpiffWorkflow"
|
url = "https://github.com/sartography/SpiffWorkflow"
|
||||||
reference = "main"
|
reference = "main"
|
||||||
resolved_reference = "86cb84d29cb25c1a5a407a702ef35cf7b469df6b"
|
resolved_reference = "c1150c68ae0eb342ed13b57544e1bb0749255cb5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spiffworkflow-connector-command"
|
name = "spiffworkflow-connector-command"
|
||||||
|
@ -16,7 +16,9 @@ class Version4(DataMigrationBase):
|
|||||||
def run(cls, process_instance: ProcessInstanceModel) -> None:
|
def run(cls, process_instance: ProcessInstanceModel) -> None:
|
||||||
# return None
|
# return None
|
||||||
try:
|
try:
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
processor = ProcessInstanceProcessor(
|
||||||
|
process_instance, include_task_data_for_completed_tasks=True, include_completed_subprocesses=True
|
||||||
|
)
|
||||||
bpmn_process_dict = processor.serialize()
|
bpmn_process_dict = processor.serialize()
|
||||||
update_data_objects(bpmn_process_dict)
|
update_data_objects(bpmn_process_dict)
|
||||||
ProcessInstanceProcessor.persist_bpmn_process_dict(
|
ProcessInstanceProcessor.persist_bpmn_process_dict(
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
import time
|
||||||
|
from collections.abc import Generator
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def benchmark(message: str) -> Generator:
|
||||||
|
"""Benchmark method useful for debugging slow stuff."""
|
||||||
|
t1 = time.perf_counter()
|
||||||
|
yield
|
||||||
|
t2 = time.perf_counter()
|
||||||
|
current_app.logger.debug(f"{message}, Time={t2 - t1}")
|
@ -367,7 +367,9 @@ def process_model_test_generate(modified_process_model_identifier: str, body: di
|
|||||||
|
|
||||||
test_case_identifier = body.get("test_case_identifier", f"test_case_for_process_instance_{process_instance_id}")
|
test_case_identifier = body.get("test_case_identifier", f"test_case_for_process_instance_{process_instance_id}")
|
||||||
process_instance = _find_process_instance_by_id_or_raise(int(process_instance_id))
|
process_instance = _find_process_instance_by_id_or_raise(int(process_instance_id))
|
||||||
processor = ProcessInstanceProcessor(process_instance, include_task_data_for_completed_tasks=True)
|
processor = ProcessInstanceProcessor(
|
||||||
|
process_instance, include_task_data_for_completed_tasks=True, include_completed_subprocesses=True
|
||||||
|
)
|
||||||
process_instance_dict = processor.serialize()
|
process_instance_dict = processor.serialize()
|
||||||
test_case_dict = ProcessModelTestGeneratorService.generate_test_from_process_instance_dict(
|
test_case_dict = ProcessModelTestGeneratorService.generate_test_from_process_instance_dict(
|
||||||
process_instance_dict, test_case_identifier=str(test_case_identifier)
|
process_instance_dict, test_case_identifier=str(test_case_identifier)
|
||||||
|
@ -421,6 +421,7 @@ class ProcessInstanceProcessor:
|
|||||||
process_id_to_run: str | None = None,
|
process_id_to_run: str | None = None,
|
||||||
additional_processing_identifier: str | None = None,
|
additional_processing_identifier: str | None = None,
|
||||||
include_task_data_for_completed_tasks: bool = False,
|
include_task_data_for_completed_tasks: bool = False,
|
||||||
|
include_completed_subprocesses: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Create a Workflow Processor based on the serialized information available in the process_instance model."""
|
"""Create a Workflow Processor based on the serialized information available in the process_instance model."""
|
||||||
self._script_engine = script_engine or self.__class__._default_script_engine
|
self._script_engine = script_engine or self.__class__._default_script_engine
|
||||||
@ -430,6 +431,7 @@ class ProcessInstanceProcessor:
|
|||||||
process_instance_model=process_instance_model,
|
process_instance_model=process_instance_model,
|
||||||
process_id_to_run=process_id_to_run,
|
process_id_to_run=process_id_to_run,
|
||||||
include_task_data_for_completed_tasks=include_task_data_for_completed_tasks,
|
include_task_data_for_completed_tasks=include_task_data_for_completed_tasks,
|
||||||
|
include_completed_subprocesses=include_completed_subprocesses,
|
||||||
)
|
)
|
||||||
|
|
||||||
def setup_processor_with_process_instance(
|
def setup_processor_with_process_instance(
|
||||||
@ -437,6 +439,7 @@ class ProcessInstanceProcessor:
|
|||||||
process_instance_model: ProcessInstanceModel,
|
process_instance_model: ProcessInstanceModel,
|
||||||
process_id_to_run: str | None = None,
|
process_id_to_run: str | None = None,
|
||||||
include_task_data_for_completed_tasks: bool = False,
|
include_task_data_for_completed_tasks: bool = False,
|
||||||
|
include_completed_subprocesses: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
tld = current_app.config["THREAD_LOCAL_DATA"]
|
tld = current_app.config["THREAD_LOCAL_DATA"]
|
||||||
tld.process_instance_id = process_instance_model.id
|
tld.process_instance_id = process_instance_model.id
|
||||||
@ -480,6 +483,8 @@ class ProcessInstanceProcessor:
|
|||||||
process_instance_model,
|
process_instance_model,
|
||||||
bpmn_process_spec,
|
bpmn_process_spec,
|
||||||
subprocesses=subprocesses,
|
subprocesses=subprocesses,
|
||||||
|
include_task_data_for_completed_tasks=include_task_data_for_completed_tasks,
|
||||||
|
include_completed_subprocesses=include_completed_subprocesses,
|
||||||
)
|
)
|
||||||
self.set_script_engine(self.bpmn_process_instance, self._script_engine)
|
self.set_script_engine(self.bpmn_process_instance, self._script_engine)
|
||||||
|
|
||||||
@ -675,11 +680,11 @@ class ProcessInstanceProcessor:
|
|||||||
include_task_data_for_completed_tasks: bool = False,
|
include_task_data_for_completed_tasks: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
json_data_hashes = set()
|
json_data_hashes = set()
|
||||||
states_to_not_rehydrate_data: list[str] = []
|
states_to_exclude_from_rehydration: list[str] = []
|
||||||
if include_task_data_for_completed_tasks:
|
if not include_task_data_for_completed_tasks:
|
||||||
states_to_not_rehydrate_data = ["COMPLETED", "CANCELLED", "ERROR"]
|
states_to_exclude_from_rehydration = ["COMPLETED", "CANCELLED", "ERROR"]
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
if task.state not in states_to_not_rehydrate_data:
|
if task.state not in states_to_exclude_from_rehydration:
|
||||||
json_data_hashes.add(task.json_data_hash)
|
json_data_hashes.add(task.json_data_hash)
|
||||||
json_data_records = JsonDataModel.query.filter(JsonDataModel.hash.in_(json_data_hashes)).all() # type: ignore
|
json_data_records = JsonDataModel.query.filter(JsonDataModel.hash.in_(json_data_hashes)).all() # type: ignore
|
||||||
json_data_mappings = {}
|
json_data_mappings = {}
|
||||||
@ -692,7 +697,7 @@ class ProcessInstanceProcessor:
|
|||||||
tasks_dict = spiff_bpmn_process_dict["subprocesses"][bpmn_subprocess_guid]["tasks"]
|
tasks_dict = spiff_bpmn_process_dict["subprocesses"][bpmn_subprocess_guid]["tasks"]
|
||||||
tasks_dict[task.guid] = task.properties_json
|
tasks_dict[task.guid] = task.properties_json
|
||||||
task_data = {}
|
task_data = {}
|
||||||
if task.state not in states_to_not_rehydrate_data:
|
if task.state not in states_to_exclude_from_rehydration:
|
||||||
task_data = json_data_mappings[task.json_data_hash]
|
task_data = json_data_mappings[task.json_data_hash]
|
||||||
tasks_dict[task.guid]["data"] = task_data
|
tasks_dict[task.guid]["data"] = task_data
|
||||||
|
|
||||||
@ -702,6 +707,7 @@ class ProcessInstanceProcessor:
|
|||||||
process_instance_model: ProcessInstanceModel,
|
process_instance_model: ProcessInstanceModel,
|
||||||
bpmn_definition_to_task_definitions_mappings: dict,
|
bpmn_definition_to_task_definitions_mappings: dict,
|
||||||
include_task_data_for_completed_tasks: bool = False,
|
include_task_data_for_completed_tasks: bool = False,
|
||||||
|
include_completed_subprocesses: bool = False,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
if process_instance_model.bpmn_process_definition_id is None:
|
if process_instance_model.bpmn_process_definition_id is None:
|
||||||
return {}
|
return {}
|
||||||
@ -767,7 +773,14 @@ class ProcessInstanceProcessor:
|
|||||||
)
|
)
|
||||||
spiff_bpmn_process_dict.update(single_bpmn_process_dict)
|
spiff_bpmn_process_dict.update(single_bpmn_process_dict)
|
||||||
|
|
||||||
bpmn_subprocesses = BpmnProcessModel.query.filter_by(top_level_process_id=bpmn_process.id).all()
|
bpmn_subprocesses_query = BpmnProcessModel.query.filter_by(top_level_process_id=bpmn_process.id)
|
||||||
|
if not include_completed_subprocesses:
|
||||||
|
bpmn_subprocesses_query = bpmn_subprocesses_query.join(
|
||||||
|
TaskModel, TaskModel.guid == BpmnProcessModel.guid
|
||||||
|
).filter(
|
||||||
|
TaskModel.state.not_in(["COMPLETED", "ERROR", "CANCELLED"]) # type: ignore
|
||||||
|
)
|
||||||
|
bpmn_subprocesses = bpmn_subprocesses_query.all()
|
||||||
bpmn_subprocess_id_to_guid_mappings = {}
|
bpmn_subprocess_id_to_guid_mappings = {}
|
||||||
for bpmn_subprocess in bpmn_subprocesses:
|
for bpmn_subprocess in bpmn_subprocesses:
|
||||||
subprocess_identifier = bpmn_subprocess.bpmn_process_definition.bpmn_identifier
|
subprocess_identifier = bpmn_subprocess.bpmn_process_definition.bpmn_identifier
|
||||||
@ -820,6 +833,7 @@ class ProcessInstanceProcessor:
|
|||||||
spec: BpmnProcessSpec | None = None,
|
spec: BpmnProcessSpec | None = None,
|
||||||
subprocesses: IdToBpmnProcessSpecMapping | None = None,
|
subprocesses: IdToBpmnProcessSpecMapping | None = None,
|
||||||
include_task_data_for_completed_tasks: bool = False,
|
include_task_data_for_completed_tasks: bool = False,
|
||||||
|
include_completed_subprocesses: bool = False,
|
||||||
) -> tuple[BpmnWorkflow, dict, dict]:
|
) -> tuple[BpmnWorkflow, dict, dict]:
|
||||||
full_bpmn_process_dict = {}
|
full_bpmn_process_dict = {}
|
||||||
bpmn_definition_to_task_definitions_mappings: dict = {}
|
bpmn_definition_to_task_definitions_mappings: dict = {}
|
||||||
@ -833,6 +847,7 @@ class ProcessInstanceProcessor:
|
|||||||
full_bpmn_process_dict = ProcessInstanceProcessor._get_full_bpmn_process_dict(
|
full_bpmn_process_dict = ProcessInstanceProcessor._get_full_bpmn_process_dict(
|
||||||
process_instance_model,
|
process_instance_model,
|
||||||
bpmn_definition_to_task_definitions_mappings,
|
bpmn_definition_to_task_definitions_mappings,
|
||||||
|
include_completed_subprocesses=include_completed_subprocesses,
|
||||||
include_task_data_for_completed_tasks=include_task_data_for_completed_tasks,
|
include_task_data_for_completed_tasks=include_task_data_for_completed_tasks,
|
||||||
)
|
)
|
||||||
# FIXME: the from_dict entrypoint in spiff will one day do this copy instead
|
# FIXME: the from_dict entrypoint in spiff will one day do this copy instead
|
||||||
@ -1247,7 +1262,9 @@ class ProcessInstanceProcessor:
|
|||||||
ProcessInstanceTmpService.add_event_to_process_instance(
|
ProcessInstanceTmpService.add_event_to_process_instance(
|
||||||
process_instance, ProcessInstanceEventType.process_instance_rewound_to_task.value, task_guid=to_task_guid
|
process_instance, ProcessInstanceEventType.process_instance_rewound_to_task.value, task_guid=to_task_guid
|
||||||
)
|
)
|
||||||
processor = ProcessInstanceProcessor(process_instance, include_task_data_for_completed_tasks=True)
|
processor = ProcessInstanceProcessor(
|
||||||
|
process_instance, include_task_data_for_completed_tasks=True, include_completed_subprocesses=True
|
||||||
|
)
|
||||||
deleted_tasks = processor.bpmn_process_instance.reset_from_task_id(UUID(to_task_guid))
|
deleted_tasks = processor.bpmn_process_instance.reset_from_task_id(UUID(to_task_guid))
|
||||||
spiff_tasks = processor.bpmn_process_instance.get_tasks()
|
spiff_tasks = processor.bpmn_process_instance.get_tasks()
|
||||||
|
|
||||||
|
@ -2030,11 +2030,10 @@ class TestProcessApi(BaseTest):
|
|||||||
)
|
)
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert process_instance.status == "error"
|
assert process_instance.status == "error"
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
processor = ProcessInstanceProcessor(process_instance, include_task_data_for_completed_tasks=True)
|
||||||
spiff_task = processor.get_task_by_bpmn_identifier("script_task_two", processor.bpmn_process_instance)
|
spiff_task = processor.get_task_by_bpmn_identifier("script_task_two", processor.bpmn_process_instance)
|
||||||
assert spiff_task is not None
|
assert spiff_task is not None
|
||||||
|
|
||||||
# TODO: remove these 2 lines if we enable no task data on rehydration again from pr-661
|
|
||||||
assert spiff_task.state == TaskState.ERROR
|
assert spiff_task.state == TaskState.ERROR
|
||||||
assert spiff_task.data == {"my_var": "THE VAR"}
|
assert spiff_task.data == {"my_var": "THE VAR"}
|
||||||
|
|
||||||
|
@ -110,7 +110,9 @@ class TestProcessInstanceMigrator(BaseTest):
|
|||||||
process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
|
process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
|
||||||
|
|
||||||
# ensure data was imported correctly and is in expected state
|
# ensure data was imported correctly and is in expected state
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
processor = ProcessInstanceProcessor(
|
||||||
|
process_instance, include_task_data_for_completed_tasks=True, include_completed_subprocesses=True
|
||||||
|
)
|
||||||
bpmn_process_dict_version_3_after_import = processor.serialize()
|
bpmn_process_dict_version_3_after_import = processor.serialize()
|
||||||
self.round_last_state_change(bpmn_process_dict_version_3)
|
self.round_last_state_change(bpmn_process_dict_version_3)
|
||||||
self.round_last_state_change(bpmn_process_dict_version_3_after_import)
|
self.round_last_state_change(bpmn_process_dict_version_3_after_import)
|
||||||
@ -125,7 +127,9 @@ class TestProcessInstanceMigrator(BaseTest):
|
|||||||
Version4.run(process_instance)
|
Version4.run(process_instance)
|
||||||
update_data_objects(bpmn_process_dict_version_4_from_spiff)
|
update_data_objects(bpmn_process_dict_version_4_from_spiff)
|
||||||
process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
|
process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
processor = ProcessInstanceProcessor(
|
||||||
|
process_instance, include_task_data_for_completed_tasks=True, include_completed_subprocesses=True
|
||||||
|
)
|
||||||
bpmn_process_dict_version_4 = processor.serialize()
|
bpmn_process_dict_version_4 = processor.serialize()
|
||||||
self.round_last_state_change(bpmn_process_dict_version_4)
|
self.round_last_state_change(bpmn_process_dict_version_4)
|
||||||
self.round_last_state_change(bpmn_process_dict_version_4_from_spiff)
|
self.round_last_state_change(bpmn_process_dict_version_4_from_spiff)
|
||||||
|
@ -209,7 +209,7 @@ class TestProcessInstanceProcessor(BaseTest):
|
|||||||
processor.do_engine_steps(save=True)
|
processor.do_engine_steps(save=True)
|
||||||
|
|
||||||
# ensure this does not raise
|
# ensure this does not raise
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
processor = ProcessInstanceProcessor(process_instance, include_completed_subprocesses=True)
|
||||||
|
|
||||||
# this task will be found within subprocesses
|
# this task will be found within subprocesses
|
||||||
spiff_task = processor.__class__.get_task_by_bpmn_identifier("level_3_script_task", processor.bpmn_process_instance)
|
spiff_task = processor.__class__.get_task_by_bpmn_identifier("level_3_script_task", processor.bpmn_process_instance)
|
||||||
@ -330,8 +330,6 @@ class TestProcessInstanceProcessor(BaseTest):
|
|||||||
task_model_to_reset_to = all_task_models_matching_top_level_subprocess_script[0]
|
task_model_to_reset_to = all_task_models_matching_top_level_subprocess_script[0]
|
||||||
assert task_model_to_reset_to is not None
|
assert task_model_to_reset_to is not None
|
||||||
assert len(process_instance.human_tasks) == 3, "expected 3 human tasks before reset"
|
assert len(process_instance.human_tasks) == 3, "expected 3 human tasks before reset"
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
|
||||||
ProcessInstanceProcessor.reset_process(process_instance, task_model_to_reset_to.guid)
|
ProcessInstanceProcessor.reset_process(process_instance, task_model_to_reset_to.guid)
|
||||||
assert len(process_instance.human_tasks) == 2, "still expected 2 human tasks after reset"
|
assert len(process_instance.human_tasks) == 2, "still expected 2 human tasks after reset"
|
||||||
|
|
||||||
@ -348,6 +346,8 @@ class TestProcessInstanceProcessor(BaseTest):
|
|||||||
task for task in ready_or_waiting_tasks if task.task_spec.name == "top_level_subprocess_script"
|
task for task in ready_or_waiting_tasks if task.task_spec.name == "top_level_subprocess_script"
|
||||||
)
|
)
|
||||||
assert top_level_subprocess_script_spiff_task is not None
|
assert top_level_subprocess_script_spiff_task is not None
|
||||||
|
# make sure we did not remove the data during the reset which can happen if include_task_data_for_completed_tasks is False
|
||||||
|
assert top_level_subprocess_script_spiff_task.data == {"set_in_top_level_script": 1}
|
||||||
processor.resume()
|
processor.resume()
|
||||||
assert (
|
assert (
|
||||||
len(process_instance.human_tasks) == 2
|
len(process_instance.human_tasks) == 2
|
||||||
@ -412,7 +412,6 @@ class TestProcessInstanceProcessor(BaseTest):
|
|||||||
"manual_task_1", processor.bpmn_process_instance
|
"manual_task_1", processor.bpmn_process_instance
|
||||||
)
|
)
|
||||||
processor.suspend()
|
processor.suspend()
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
|
||||||
ProcessInstanceProcessor.reset_process(process_instance, str(reset_to_spiff_task.id))
|
ProcessInstanceProcessor.reset_process(process_instance, str(reset_to_spiff_task.id))
|
||||||
process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
|
process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
|
||||||
human_task_one = process_instance.active_human_tasks[0]
|
human_task_one = process_instance.active_human_tasks[0]
|
||||||
@ -527,9 +526,11 @@ class TestProcessInstanceProcessor(BaseTest):
|
|||||||
|
|
||||||
# recreate variables to ensure all bpmn json was recreated from scratch from the db
|
# recreate variables to ensure all bpmn json was recreated from scratch from the db
|
||||||
process_instance_relookup = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
|
process_instance_relookup = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
|
||||||
processor_final = ProcessInstanceProcessor(process_instance_relookup)
|
processor_last_tasks = ProcessInstanceProcessor(process_instance_relookup)
|
||||||
processor_final.do_engine_steps(save=True, execution_strategy_name="greedy")
|
processor_last_tasks.do_engine_steps(save=True, execution_strategy_name="greedy")
|
||||||
|
|
||||||
|
process_instance_relookup = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
|
||||||
|
processor_final = ProcessInstanceProcessor(process_instance_relookup, include_completed_subprocesses=True)
|
||||||
assert process_instance_relookup.status == "complete"
|
assert process_instance_relookup.status == "complete"
|
||||||
|
|
||||||
data_set_1 = {"set_in_top_level_script": 1}
|
data_set_1 = {"set_in_top_level_script": 1}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user