diff --git a/SpiffWorkflow/bpmn/serializer/migration/version_1_4.py b/SpiffWorkflow/bpmn/serializer/migration/version_1_4.py new file mode 100644 index 00000000..8a4cc7d1 --- /dev/null +++ b/SpiffWorkflow/bpmn/serializer/migration/version_1_4.py @@ -0,0 +1,13 @@ + +def update_mi_states(dct): + + typenames = ['StandardLoopTask', 'SequentialMultiInstanceTask', 'ParallelMultiInstanceTask'] + def update(tasks, task_specs): + for task in tasks: + task_spec = task_specs.get(task['task_spec'], {}) + if task['state'] == 8 and task_spec['typename'] in typenames: + task['state'] = 32 + + for up in dct['subprocesses'].values(): + update(sp['tasks'].values(), sp['spec']['task_specs']) + update(dct['tasks'].values(), dct['spec']['task_specs']) diff --git a/SpiffWorkflow/bpmn/serializer/migration/version_migration.py b/SpiffWorkflow/bpmn/serializer/migration/version_migration.py index cf193283..15b39a58 100644 --- a/SpiffWorkflow/bpmn/serializer/migration/version_migration.py +++ b/SpiffWorkflow/bpmn/serializer/migration/version_migration.py @@ -35,8 +35,32 @@ add_new_typenames, update_data_objects, ) +from .version_1_4 import update_mi_states + +def from_version_1_3(dct): + """Upgrade serialization from v1.3 to v1.4 + + Multiinstance tasks now rely on events rather than polling to merge children, so once + they are reached, they should be STARTED rather than WAITING. + """ + dct['VERSION'] = "1.3" + update_mi_states(dct) def from_version_1_2(dct): + """Upgrade serialization from v.1.2 to v.1.3 + + The internal/external distinction on event definitions was replaced with the ability to + target a specific workflow. + + Boundary event parent gateway tasks ave been replaced with a gateway structure. + + The creation of an unnecessary root task was removed; the workflow spec's start task is + used as the root instead. + + BpmnWorkflows and BpmnSubworkflows were split into to classes. + + Data objects are now stored on the topmost workflow where they are defined. + """ dct['VERSION'] = "1.3" update_event_definition_attributes(dct) remove_boundary_event_parent(dct) @@ -44,7 +68,6 @@ def from_version_1_2(dct): add_new_typenames(dct) update_data_objects(dct) - def from_version_1_1(dct): """ Upgrade v1.1 serialization to v1.2. @@ -98,4 +121,5 @@ def from_version_1_0(dct): '1.0': from_version_1_0, '1.1': from_version_1_1, '1.2': from_version_1_2, + '1.3': from_version_1_3, } diff --git a/SpiffWorkflow/bpmn/serializer/workflow.py b/SpiffWorkflow/bpmn/serializer/workflow.py index c76965f6..a13af7d0 100644 --- a/SpiffWorkflow/bpmn/serializer/workflow.py +++ b/SpiffWorkflow/bpmn/serializer/workflow.py @@ -25,7 +25,7 @@ from .config import DEFAULT_CONFIG # This is the default version set on the workflow, it can be overridden in init -VERSION = "1.3" +VERSION = "1.4" class BpmnWorkflowSerializer: diff --git a/tests/SpiffWorkflow/bpmn/data/serialization/v1.3-mi-states.json b/tests/SpiffWorkflow/bpmn/data/serialization/v1.3-mi-states.json new file mode 100644 index 00000000..8e192887 --- /dev/null +++ b/tests/SpiffWorkflow/bpmn/data/serialization/v1.3-mi-states.json @@ -0,0 +1,378 @@ +{ + "serializer_version": "1.3", + "data":{}, + "correlations":{}, + "last_task":"d022bf75-49a5-4c05-a46d-e3bd34029b1e", + "success":true, + "tasks":{ + "8d0c2d46-0de5-4616-a7ce-6d7f43e9dd3d":{ + "id":"8d0c2d46-0de5-4616-a7ce-6d7f43e9dd3d", + "parent":null, + "children":[ + "edea6aef-cbba-4fb8-a9fa-566b8f3e9df5" + ], + "last_state_change":1716952026.0028443, + "state":64, + "task_spec":"Start", + "triggered":false, + "internal_data":{}, + "data":{ + "input_data":[ + 1, + 2, + 3 + ] + }, + "typename":"Task" + }, + "edea6aef-cbba-4fb8-a9fa-566b8f3e9df5":{ + "id":"edea6aef-cbba-4fb8-a9fa-566b8f3e9df5", + "parent":"8d0c2d46-0de5-4616-a7ce-6d7f43e9dd3d", + "children":[ + "32cf0153-96b2-498f-8969-d0147e26af08" + ], + "last_state_change":1716952026.003962, + "state":64, + "task_spec":"StartEvent_1", + "triggered":false, + "internal_data":{ + "event_fired":true + }, + "data":{ + "input_data":[ + 1, + 2, + 3 + ] + }, + "typename":"Task" + }, + "32cf0153-96b2-498f-8969-d0147e26af08":{ + "id":"32cf0153-96b2-498f-8969-d0147e26af08", + "parent":"edea6aef-cbba-4fb8-a9fa-566b8f3e9df5", + "children":[ + "a5e49220-f190-4580-b1f0-d728a47ea87c", + "56892ee3-26fb-45f7-83af-d3f7dfd29e16", + "d022bf75-49a5-4c05-a46d-e3bd34029b1e", + "073c949f-5ee2-4132-8198-f7a20b1eaf91" + ], + "last_state_change":1716952026.0043452, + "state":8, + "task_spec":"any_task", + "triggered":false, + "internal_data":{ + "started":true, + "merged":[ + "56892ee3-26fb-45f7-83af-d3f7dfd29e16", + "d022bf75-49a5-4c05-a46d-e3bd34029b1e" + ] + }, + "data":{ + "input_data":[ + 1, + 2, + 3 + ], + "output_data":[ + 2, + 4 + ] + }, + "typename":"Task" + }, + "a5e49220-f190-4580-b1f0-d728a47ea87c":{ + "id":"a5e49220-f190-4580-b1f0-d728a47ea87c", + "parent":"32cf0153-96b2-498f-8969-d0147e26af08", + "children":[ + "9f9e2c26-d33e-476c-987f-98d08da790db" + ], + "last_state_change":1716952026.0009453, + "state":4, + "task_spec":"Event_1xk7z3g", + "triggered":false, + "internal_data":{}, + "data":{}, + "typename":"Task" + }, + "9f9e2c26-d33e-476c-987f-98d08da790db":{ + "id":"9f9e2c26-d33e-476c-987f-98d08da790db", + "parent":"a5e49220-f190-4580-b1f0-d728a47ea87c", + "children":[ + "39da9add-e22c-4a43-9719-3970c78194e7" + ], + "last_state_change":1716952026.001038, + "state":4, + "task_spec":"main.EndJoin", + "triggered":false, + "internal_data":{}, + "data":{}, + "typename":"Task" + }, + "39da9add-e22c-4a43-9719-3970c78194e7":{ + "id":"39da9add-e22c-4a43-9719-3970c78194e7", + "parent":"9f9e2c26-d33e-476c-987f-98d08da790db", + "children":[], + "last_state_change":1716952026.001173, + "state":4, + "task_spec":"End", + "triggered":false, + "internal_data":{}, + "data":{}, + "typename":"Task" + }, + "56892ee3-26fb-45f7-83af-d3f7dfd29e16":{ + "id":"56892ee3-26fb-45f7-83af-d3f7dfd29e16", + "parent":"32cf0153-96b2-498f-8969-d0147e26af08", + "children":[], + "last_state_change":1716952026.0068834, + "state":64, + "task_spec":"any_task [child]", + "triggered":true, + "internal_data":{ + "key_or_index":0 + }, + "data":{ + "input_item":1, + "input_data":[ + 1, + 2, + 3 + ], + "output_data":[], + "output_item":2 + }, + "typename":"Task" + }, + "d022bf75-49a5-4c05-a46d-e3bd34029b1e":{ + "id":"d022bf75-49a5-4c05-a46d-e3bd34029b1e", + "parent":"32cf0153-96b2-498f-8969-d0147e26af08", + "children":[], + "last_state_change":1716952029.4668372, + "state":64, + "task_spec":"any_task [child]", + "triggered":true, + "internal_data":{ + "key_or_index":1 + }, + "data":{ + "input_item":2, + "input_data":[ + 1, + 2, + 3 + ], + "output_data":[], + "output_item":4 + }, + "typename":"Task" + }, + "073c949f-5ee2-4132-8198-f7a20b1eaf91":{ + "id":"073c949f-5ee2-4132-8198-f7a20b1eaf91", + "parent":"32cf0153-96b2-498f-8969-d0147e26af08", + "children":[], + "last_state_change":1716952026.0049052, + "state":16, + "task_spec":"any_task [child]", + "triggered":true, + "internal_data":{ + "key_or_index":2 + }, + "data":{ + "input_item":3, + "input_data":[ + 1, + 2, + 3 + ], + "output_data":[] + }, + "typename":"Task" + } + }, + "root":"8d0c2d46-0de5-4616-a7ce-6d7f43e9dd3d", + "spec":{ + "name":"main", + "description":"main", + "file":"/home/essweine/work/sartography/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/parallel_multiinstance_loop_input.bpmn", + "task_specs":{ + "Start":{ + "name":"Start", + "description":"BPMN Task", + "manual":false, + "lookahead":2, + "inputs":[], + "outputs":[ + "StartEvent_1" + ], + "bpmn_id":null, + "bpmn_name":null, + "lane":null, + "documentation":null, + "data_input_associations":[], + "data_output_associations":[], + "io_specification":null, + "typename":"BpmnStartTask" + }, + "main.EndJoin":{ + "name":"main.EndJoin", + "description":"BPMN Task", + "manual":false, + "lookahead":2, + "inputs":[ + "Event_1xk7z3g" + ], + "outputs":[ + "End" + ], + "bpmn_id":null, + "bpmn_name":null, + "lane":null, + "documentation":null, + "data_input_associations":[], + "data_output_associations":[], + "io_specification":null, + "typename":"_EndJoin" + }, + "End":{ + "name":"End", + "description":"BPMN Task", + "manual":false, + "lookahead":2, + "inputs":[ + "main.EndJoin" + ], + "outputs":[], + "bpmn_id":null, + "bpmn_name":null, + "lane":null, + "documentation":null, + "data_input_associations":[], + "data_output_associations":[], + "io_specification":null, + "typename":"SimpleBpmnTask" + }, + "StartEvent_1":{ + "name":"StartEvent_1", + "description":"Default Start Event", + "manual":false, + "lookahead":2, + "inputs":[ + "Start" + ], + "outputs":[ + "any_task" + ], + "bpmn_id":"StartEvent_1", + "bpmn_name":null, + "lane":null, + "documentation":null, + "data_input_associations":[], + "data_output_associations":[], + "io_specification":null, + "event_definition":{ + "description":"Default", + "name":null, + "typename":"NoneEventDefinition" + }, + "typename":"StartEvent", + "extensions":{} + }, + "any_task":{ + "name":"any_task", + "description":"Parallel MultiInstance", + "manual":false, + "lookahead":2, + "inputs":[ + "StartEvent_1" + ], + "outputs":[ + "Event_1xk7z3g" + ], + "bpmn_id":"any_task", + "bpmn_name":null, + "lane":null, + "documentation":null, + "data_input_associations":[], + "data_output_associations":[], + "io_specification":null, + "task_spec":"any_task [child]", + "cardinality":null, + "data_input":{ + "bpmn_id":"input_data", + "bpmn_name":null, + "typename":"TaskDataReference" + }, + "data_output":{ + "bpmn_id":"output_data", + "bpmn_name":null, + "typename":"TaskDataReference" + }, + "input_item":{ + "bpmn_id":"input_item", + "bpmn_name":"input item", + "typename":"TaskDataReference" + }, + "output_item":{ + "bpmn_id":"output_item", + "bpmn_name":"output item", + "typename":"TaskDataReference" + }, + "condition":null, + "typename":"ParallelMultiInstanceTask" + }, + "any_task [child]":{ + "name":"any_task [child]", + "description":"Task", + "manual":true, + "lookahead":2, + "inputs":[ + "any_task" + ], + "outputs":[], + "bpmn_id":"any_task", + "bpmn_name":"Any Task", + "lane":null, + "documentation":null, + "data_input_associations":[], + "data_output_associations":[], + "io_specification":null, + "typename":"NoneTask", + "extensions":{} + }, + "Event_1xk7z3g":{ + "name":"Event_1xk7z3g", + "description":"Default End Event", + "manual":false, + "lookahead":2, + "inputs":[ + "any_task" + ], + "outputs":[ + "main.EndJoin" + ], + "bpmn_id":"Event_1xk7z3g", + "bpmn_name":null, + "lane":null, + "documentation":null, + "data_input_associations":[], + "data_output_associations":[], + "io_specification":null, + "event_definition":{ + "description":"Default", + "name":null, + "typename":"NoneEventDefinition" + }, + "typename":"EndEvent", + "extensions":{} + } + }, + "io_specification":null, + "data_objects":{}, + "correlation_keys":{}, + "typename":"BpmnProcessSpec" + }, + "subprocess_specs":{}, + "subprocesses":{}, + "bpmn_events":[], + "typename":"BpmnWorkflow" +} diff --git a/tests/SpiffWorkflow/bpmn/serializer/VersionMigrationTest.py b/tests/SpiffWorkflow/bpmn/serializer/VersionMigrationTest.py index 3b914b50..f0f5d33c 100644 --- a/tests/SpiffWorkflow/bpmn/serializer/VersionMigrationTest.py +++ b/tests/SpiffWorkflow/bpmn/serializer/VersionMigrationTest.py @@ -172,3 +172,36 @@ def test_update_nested_data_objects(self): self.assertNotIn('sub_level_data_object_two', call_sub.data_objects) self.assertIn('sub_level_data_object_three', call_sub.data_objects) self.assertNotIn('sub_level_data_object_three', process_sub.data_objects) + +class Version_1_3_Test(BaseTestCase): + + def test_update_mi_states(self): + + wf = self.deserialize_workflow('v1.3-mi-states.json') + + any_task = wf.get_next_task(spec_name='any_task') + task_info = any_task.task_spec.task_info(any_task) + instance_map = task_info['instance_map'] + + self.assertEqual(len(wf.get_tasks(state=TaskState.WAITING)), 0) + + ready_tasks = wf.get_tasks(state=TaskState.READY, manual=True) + self.assertEqual(len(ready_tasks), 1) + while len(ready_tasks) > 0: + task = ready_tasks[0] + task_info = task.task_spec.task_info(task) + self.assertEqual(task.task_spec.name, 'any_task [child]') + self.assertIn('input_item', task.data) + self.assertEqual(instance_map[task_info['instance']], str(task.id)) + task.data['output_item'] = task.data['input_item'] * 2 + task.run() + ready_tasks = wf.get_tasks(state=TaskState.READY, manual=True) + wf.refresh_waiting_tasks() + wf.do_engine_steps() + + any_task = wf.get_next_task(spec_name='any_task') + task_info = any_task.task_spec.task_info(any_task) + self.assertEqual(len(task_info['completed']), 3) + self.assertEqual(len(task_info['running']), 0) + self.assertEqual(len(task_info['future']), 0) + self.assertTrue(wf.is_completed())