diff --git a/SpiffWorkflow/spiff/parser/process.py b/SpiffWorkflow/spiff/parser/process.py index 4abdb96..2ec536f 100644 --- a/SpiffWorkflow/spiff/parser/process.py +++ b/SpiffWorkflow/spiff/parser/process.py @@ -2,9 +2,9 @@ from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser from SpiffWorkflow.bpmn.parser.BpmnParser import full_tag from SpiffWorkflow.bpmn.specs.events import StartEvent, EndEvent, IntermediateThrowEvent, BoundaryEvent, IntermediateCatchEvent -from SpiffWorkflow.spiff.specs import NoneTask, ManualTask, UserTask, SubWorkflowTask, TransactionSubprocess, CallActivity, ServiceTask +from SpiffWorkflow.spiff.specs import NoneTask, ManualTask, UserTask, ScriptTask, SubWorkflowTask, TransactionSubprocess, CallActivity, ServiceTask from SpiffWorkflow.spiff.specs.events.event_types import SendTask, ReceiveTask -from SpiffWorkflow.spiff.parser.task_spec import SpiffTaskParser, SubWorkflowParser, CallActivityParser, ServiceTaskParser +from SpiffWorkflow.spiff.parser.task_spec import SpiffTaskParser, SubWorkflowParser, CallActivityParser, ServiceTaskParser, ScriptTaskParser from SpiffWorkflow.spiff.parser.event_parsers import (SpiffStartEventParser, SpiffEndEventParser, SpiffBoundaryEventParser, SpiffIntermediateCatchEventParser, SpiffIntermediateThrowEventParser, SpiffSendTaskParser, SpiffReceiveTaskParser) from SpiffWorkflow.dmn.specs import BusinessRuleTask @@ -17,6 +17,7 @@ class SpiffBpmnParser(BpmnDmnParser): full_tag('task'): (SpiffTaskParser, NoneTask), full_tag('userTask'): (SpiffTaskParser, UserTask), full_tag('manualTask'): (SpiffTaskParser, ManualTask), + full_tag('scriptTask'): (ScriptTaskParser, ScriptTask), full_tag('subProcess'): (SubWorkflowParser, SubWorkflowTask), full_tag('transaction'): (SubWorkflowParser, TransactionSubprocess), full_tag('callActivity'): (CallActivityParser, CallActivity), diff --git a/SpiffWorkflow/spiff/parser/task_spec.py b/SpiffWorkflow/spiff/parser/task_spec.py index 32e8ecf..da3961d 100644 --- a/SpiffWorkflow/spiff/parser/task_spec.py +++ b/SpiffWorkflow/spiff/parser/task_spec.py @@ -6,6 +6,7 @@ from SpiffWorkflow.bpmn.parser.task_parsers import SubprocessParser from SpiffWorkflow.bpmn.parser.util import xpath_eval SPIFFWORKFLOW_MODEL_NS = 'http://spiffworkflow.org/bpmn/schema/1.0/core' +SPIFFWORKFLOW_MODEL_PREFIX = 'spiffworkflow' class SpiffTaskParser(TaskParser): @@ -20,36 +21,55 @@ class SpiffTaskParser(TaskParser): # Too bad doing this works in such a stupid way. # We should set a namespace and automatically do this. extensions = {} - extra_ns = {'spiffworkflow': SPIFFWORKFLOW_MODEL_NS} + extra_ns = {SPIFFWORKFLOW_MODEL_PREFIX: SPIFFWORKFLOW_MODEL_NS} xpath = xpath_eval(node, extra_ns) - extension_nodes = xpath('.//bpmn:extensionElements/spiffworkflow:*') + extension_nodes = xpath(f'.//bpmn:extensionElements/{SPIFFWORKFLOW_MODEL_PREFIX}:*') for node in extension_nodes: name = etree.QName(node).localname if name == 'properties': extensions['properties'] = SpiffTaskParser._parse_properties(node) + elif name == 'unitTests': + extensions['unitTests'] = SpiffTaskParser._parse_script_unit_tests(node) elif name == 'serviceTaskOperator': extensions['serviceTaskOperator'] = SpiffTaskParser._parse_servicetask_operator(node) else: extensions[name] = node.text return extensions - @staticmethod - def _parse_properties(node): - extra_ns = {'spiffworkflow': SPIFFWORKFLOW_MODEL_NS} - xpath = xpath_eval(node, extra_ns) - property_nodes = xpath('.//spiffworkflow:property') + @classmethod + def _node_children_by_tag_name(cls, node, tag_name): + xpath = cls._spiffworkflow_ready_xpath_for_node(node) + return xpath(f'.//{SPIFFWORKFLOW_MODEL_PREFIX}:{tag_name}') + + @classmethod + def _parse_properties(cls, node): + property_nodes = cls._node_children_by_tag_name(node, 'property') properties = {} for prop_node in property_nodes: properties[prop_node.attrib['name']] = prop_node.attrib['value'] return properties @staticmethod - def _parse_servicetask_operator(node): + def _spiffworkflow_ready_xpath_for_node(node): + extra_ns = {SPIFFWORKFLOW_MODEL_PREFIX: SPIFFWORKFLOW_MODEL_NS} + return xpath_eval(node, extra_ns) + + @classmethod + def _parse_script_unit_tests(cls, node): + unit_test_nodes = cls._node_children_by_tag_name(node, 'unitTest') + unit_tests = [] + for unit_test_node in unit_test_nodes: + unit_test_dict = {"id": unit_test_node.attrib['id']} + unit_test_dict['inputJson'] = cls._node_children_by_tag_name(unit_test_node, 'inputJson')[0].text + unit_test_dict['expectedOutputJson'] = cls._node_children_by_tag_name(unit_test_node, 'expectedOutputJson')[0].text + unit_tests.append(unit_test_dict) + return unit_tests + + @classmethod + def _parse_servicetask_operator(cls, node): name = node.attrib['id'] result_variable = node.get('resultVariable', None) - extra_ns = {'spiffworkflow': SPIFFWORKFLOW_MODEL_NS} - xpath = xpath_eval(node, extra_ns) - parameter_nodes = xpath('.//spiffworkflow:parameter') + parameter_nodes = cls._node_children_by_tag_name(node, 'parameter') operator = {'name': name, 'resultVariable': result_variable} parameters = {} for param_node in parameter_nodes: @@ -92,6 +112,20 @@ class SubWorkflowParser(SpiffTaskParser): postscript=postscript) +class ScriptTaskParser(SpiffTaskParser): + def create_task(self): + extensions = self.parse_extensions() + script = None + for child_node in self.node: + if child_node.tag.endswith('script'): + script = child_node.text + # import pdb; pdb.set_trace() + return self.spec_class( + self.spec, self.get_task_spec_name(), script, + lane=self.lane, position=self.position, + description=self.node.get('name', None)) + + class CallActivityParser(SpiffTaskParser): def create_task(self): diff --git a/SpiffWorkflow/spiff/serializer/__init__.py b/SpiffWorkflow/spiff/serializer/__init__.py index 4ba82d6..364c3eb 100644 --- a/SpiffWorkflow/spiff/serializer/__init__.py +++ b/SpiffWorkflow/spiff/serializer/__init__.py @@ -1,4 +1,4 @@ -from .task_spec_converters import NoneTaskConverter, ManualTaskConverter, UserTaskConverter +from .task_spec_converters import NoneTaskConverter, ManualTaskConverter, UserTaskConverter, ScriptTaskConverter from .task_spec_converters import TransactionSubprocessConverter, CallActivityTaskConverter, SubWorkflowTaskConverter from .task_spec_converters import StartEventConverter, EndEventConverter, IntermediateCatchEventConverter, IntermediateThrowEventConverter, \ BoundaryEventConverter, SendTaskConverter, ReceiveTaskConverter, ServiceTaskConverter diff --git a/SpiffWorkflow/spiff/serializer/task_spec_converters.py b/SpiffWorkflow/spiff/serializer/task_spec_converters.py index 8c25970..5b24278 100644 --- a/SpiffWorkflow/spiff/serializer/task_spec_converters.py +++ b/SpiffWorkflow/spiff/serializer/task_spec_converters.py @@ -2,7 +2,7 @@ from functools import partial from SpiffWorkflow.bpmn.serializer.bpmn_converters import BpmnTaskSpecConverter from SpiffWorkflow.bpmn.specs.events import EndEvent, StartEvent, IntermediateThrowEvent, IntermediateCatchEvent, BoundaryEvent -from SpiffWorkflow.spiff.specs import NoneTask, ManualTask, UserTask, ServiceTask, SubWorkflowTask, TransactionSubprocess, CallActivity +from SpiffWorkflow.spiff.specs import NoneTask, ManualTask, UserTask, ScriptTask, ServiceTask, SubWorkflowTask, TransactionSubprocess, CallActivity from SpiffWorkflow.spiff.specs.events import SendTask, ReceiveTask from SpiffWorkflow.spiff.specs.events.event_definitions import MessageEventDefinition @@ -35,6 +35,16 @@ class UserTaskConverter(SpiffBpmnTaskConverter): super().__init__(UserTask, data_converter) +class ScriptTaskConverter(SpiffBpmnTaskConverter): + def __init__(self, data_converter=None): + super().__init__(ScriptTask, data_converter) + + def to_dict(self, spec): + dct = super().to_dict(spec) + dct['script'] = spec.script + return dct + + class ServiceTaskConverter(SpiffBpmnTaskConverter): def __init__(self, data_converter=None): super().__init__(ServiceTask, data_converter) diff --git a/SpiffWorkflow/spiff/specs/__init__.py b/SpiffWorkflow/spiff/specs/__init__.py index 5378a5b..1e18b63 100644 --- a/SpiffWorkflow/spiff/specs/__init__.py +++ b/SpiffWorkflow/spiff/specs/__init__.py @@ -2,4 +2,5 @@ from .manual_task import ManualTask from .none_task import NoneTask from .subworkflow_task import SubWorkflowTask, TransactionSubprocess, CallActivity from .user_task import UserTask +from .script_task import ScriptTask from .service_task import ServiceTask diff --git a/SpiffWorkflow/spiff/specs/script_task.py b/SpiffWorkflow/spiff/specs/script_task.py new file mode 100644 index 0000000..c56e7e8 --- /dev/null +++ b/SpiffWorkflow/spiff/specs/script_task.py @@ -0,0 +1,6 @@ +from SpiffWorkflow.spiff.specs.spiff_task import SpiffBpmnTask +from SpiffWorkflow.bpmn.specs.ScriptTask import ScriptTask as BpmnScriptTask + + +class ScriptTask(BpmnScriptTask, SpiffBpmnTask): + pass diff --git a/tests/SpiffWorkflow/spiff/BaseTestCase.py b/tests/SpiffWorkflow/spiff/BaseTestCase.py index 92c23eb..248f84a 100644 --- a/tests/SpiffWorkflow/spiff/BaseTestCase.py +++ b/tests/SpiffWorkflow/spiff/BaseTestCase.py @@ -3,7 +3,7 @@ import os from SpiffWorkflow.spiff.parser import SpiffBpmnParser from SpiffWorkflow.spiff.serializer import NoneTaskConverter, \ - ManualTaskConverter, UserTaskConverter, \ + ManualTaskConverter, UserTaskConverter, ScriptTaskConverter, \ SubWorkflowTaskConverter, TransactionSubprocessConverter, \ CallActivityTaskConverter, \ StartEventConverter, EndEventConverter, BoundaryEventConverter, \ @@ -16,7 +16,7 @@ from SpiffWorkflow.bpmn.serializer import BpmnWorkflowSerializer from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase wf_spec_converter = BpmnWorkflowSerializer.configure_workflow_spec_converter([ - NoneTaskConverter, ManualTaskConverter, UserTaskConverter, + NoneTaskConverter, ManualTaskConverter, UserTaskConverter, ScriptTaskConverter, SubWorkflowTaskConverter, TransactionSubprocessConverter, CallActivityTaskConverter, StartEventConverter, EndEventConverter, BoundaryEventConverter, SendTaskConverter, ReceiveTaskConverter, IntermediateCatchEventConverter, IntermediateThrowEventConverter, BusinessRuleTaskConverter, diff --git a/tests/SpiffWorkflow/spiff/ScriptUnitTestExtensionsTest.py b/tests/SpiffWorkflow/spiff/ScriptUnitTestExtensionsTest.py new file mode 100644 index 0000000..87c8335 --- /dev/null +++ b/tests/SpiffWorkflow/spiff/ScriptUnitTestExtensionsTest.py @@ -0,0 +1,46 @@ +from SpiffWorkflow.task import TaskState +from .BaseTestCase import BaseTestCase +from SpiffWorkflow.bpmn.workflow import BpmnWorkflow + +# Assure we correctly parse and pass on the Spiffworkflow properties in +# an extension. +class ScriptUnitTestExtensionsTest(BaseTestCase): + + def testTask(self): + self.task_test() + + def testTaskSaveRestore(self): + self.task_test(True) + + def task_test(self, save_restore=False): + + spec, subprocesses = self.load_workflow_spec('script_task_with_unit_tests.bpmn', 'Process_ScriptTaskWithUnitTests') + self.workflow = BpmnWorkflow(spec, subprocesses) + self.workflow.do_engine_steps() + if save_restore: + self.save_restore() + + # unitTests should be a list of dicts + expected_unit_tests_wrapper_class_name = 'list' + expected_unit_test_class_name = 'dict' + + script_with_unit_tests = [t for t in self.workflow.get_tasks() if + t.task_spec.name == 'script_with_unit_test_id'][0] + print(f"script_with_unit_tests.task_spec.extensions: {script_with_unit_tests.task_spec.extensions}") + extensions = script_with_unit_tests.task_spec.extensions + unit_test_extensions = extensions['unitTests'] + print(f"unit_test_extensions: {unit_test_extensions}") + print(f"unit_test_extensions.class: {unit_test_extensions.__class__.__name__}") + unit_test_extensions_class_name = unit_test_extensions.__class__.__name__ + self.assertEqual(unit_test_extensions_class_name, expected_unit_tests_wrapper_class_name) + self.assertEqual(len(unit_test_extensions), 2) + first_unit_test = unit_test_extensions[0] + self.assertEqual(first_unit_test.__class__.__name__, expected_unit_test_class_name) + expected_first_unit_test = {'id': 'sets_hey_to_true_if_hey_is_false', + 'inputJson': '{"hey": false}', 'expectedOutputJson': '{"hey": true}'} + self.assertDictEqual(first_unit_test, expected_first_unit_test) + # self.assertEqual(len(unit_test_extensions), 2) + # self.assertDictEqual({'formJsonSchemaFilename': 'my_json_jschema.json', + # 'formUiSchemaFilename': 'my_ui_jschema.json'}, + # task.task_spec.extensions) + diff --git a/tests/SpiffWorkflow/spiff/SpiffPropertiesTest.py b/tests/SpiffWorkflow/spiff/SpiffPropertiesTest.py index d029f35..9087aae 100644 --- a/tests/SpiffWorkflow/spiff/SpiffPropertiesTest.py +++ b/tests/SpiffWorkflow/spiff/SpiffPropertiesTest.py @@ -4,7 +4,7 @@ from SpiffWorkflow.bpmn.workflow import BpmnWorkflow # Assure we correctly parse and pass on the Spiffworkflow properties in # an extension. -class SpiffWorkflowProperties(BaseTestCase): +class SpiffPropertiesTest(BaseTestCase): def testTask(self): self.task_test() diff --git a/tests/SpiffWorkflow/spiff/data/script_task_with_unit_tests.bpmn b/tests/SpiffWorkflow/spiff/data/script_task_with_unit_tests.bpmn new file mode 100644 index 0000000..d2873b8 --- /dev/null +++ b/tests/SpiffWorkflow/spiff/data/script_task_with_unit_tests.bpmn @@ -0,0 +1,69 @@ + + + + + Flow_10jwwqy + + + + Flow_0htxke7 + + + + + + + {"hey": false} + {"hey": true} + + + {} + {"something_else": true} + + + + Flow_0niwe1y + Flow_0htxke7 + if 'hey' in locals(): + hey = True +else: + something_else = True + + + + Flow_10jwwqy + Flow_0niwe1y + hey = False + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +