You can now add multiple files to a workflow spec, and if properly linked, you can associate a DMN file with a BPMN to process decision tables.
This commit is contained in:
parent
532c00fde5
commit
95b75f864d
|
@ -667,7 +667,7 @@
|
|||
"spiffworkflow": {
|
||||
"editable": true,
|
||||
"git": "https://github.com/sartography/SpiffWorkflow.git",
|
||||
"ref": "7dd5bfb15fc227c2b5a36c052f987adf9c0df1a8"
|
||||
"ref": "7640c6e32d3894b13f8a078849922cf7cb6884a5"
|
||||
},
|
||||
"sqlalchemy": {
|
||||
"hashes": [
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1elv5t1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.4.1">
|
||||
<bpmn:process id="Process_15vbyda" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>SequenceFlow_1ma1wxb</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1ma1wxb" sourceRef="StartEvent_1" targetRef="get_num_presents" />
|
||||
<bpmn:userTask id="get_num_presents" name="Get number of presents" camunda:formKey="present_question">
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="num_presents" label="How many presents will my dog Ginger leave for me today?" type="long" defaultValue="0" />
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>SequenceFlow_1ma1wxb</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_1uxaqwp</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1uxaqwp" sourceRef="get_num_presents" targetRef="Task_0sgafty" />
|
||||
<bpmn:businessRuleTask id="Task_0sgafty" name="Message based on number of presents" camunda:decisionRef="presents_to_message">
|
||||
<bpmn:incoming>SequenceFlow_1uxaqwp</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_0grui6f</bpmn:outgoing>
|
||||
</bpmn:businessRuleTask>
|
||||
<bpmn:endEvent id="EndEvent_0tsqkyu">
|
||||
<bpmn:incoming>SequenceFlow_0grui6f</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_0grui6f" sourceRef="Task_0sgafty" targetRef="EndEvent_0tsqkyu" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_15vbyda">
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="179" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1ma1wxb_di" bpmnElement="SequenceFlow_1ma1wxb">
|
||||
<di:waypoint x="215" y="117" />
|
||||
<di:waypoint x="270" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="UserTask_15w5gb3_di" bpmnElement="get_num_presents">
|
||||
<dc:Bounds x="270" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1uxaqwp_di" bpmnElement="SequenceFlow_1uxaqwp">
|
||||
<di:waypoint x="370" y="117" />
|
||||
<di:waypoint x="430" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="BusinessRuleTask_10c5wgr_di" bpmnElement="Task_0sgafty">
|
||||
<dc:Bounds x="430" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="EndEvent_0tsqkyu_di" bpmnElement="EndEvent_0tsqkyu">
|
||||
<dc:Bounds x="592" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_0grui6f_di" bpmnElement="SequenceFlow_0grui6f">
|
||||
<di:waypoint x="530" y="117" />
|
||||
<di:waypoint x="592" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/DMN/20151101/dmn.xsd" xmlns:biodi="http://bpmn.io/schema/dmn/biodi/1.0" id="Definitions_1hao5sb" name="DRD" namespace="http://camunda.org/schema/1.0/dmn" exporter="Camunda Modeler" exporterVersion="3.4.1">
|
||||
<decision id="presents_to_message" name="Decision 1">
|
||||
<extensionElements>
|
||||
<biodi:bounds x="150" y="150" width="180" height="80" />
|
||||
</extensionElements>
|
||||
<decisionTable id="decisionTable_1">
|
||||
<input id="input_1" label="num_presents">
|
||||
<inputExpression id="inputExpression_1" typeRef="long">
|
||||
<text></text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<output id="output_1" label="message" name="message" typeRef="string" />
|
||||
<rule id="DecisionRule_0gl355z">
|
||||
<inputEntry id="UnaryTests_06x22gk">
|
||||
<text>0</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_0yuxzxi">
|
||||
<text>"GREAT Dog! I love you."</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_1s6l5b6">
|
||||
<inputEntry id="UnaryTests_1oyo6k0">
|
||||
<text>1</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_09t5r62">
|
||||
<text>"Oh, Ginger."</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_1dvd34d">
|
||||
<inputEntry id="UnaryTests_1k557bj">
|
||||
<text>2</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_1n1eo23">
|
||||
<text>"Sheesh, you silly dog."</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_0tqqjg9">
|
||||
<inputEntry id="UnaryTests_0dnd50d">
|
||||
<text>> 2</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_0fk5uhh">
|
||||
<text>"!@#$!@#$"</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
</decisionTable>
|
||||
</decision>
|
||||
</definitions>
|
|
@ -1,13 +1,15 @@
|
|||
import xml.etree.ElementTree as ElementTree
|
||||
|
||||
from SpiffWorkflow.bpmn.BpmnScriptEngine import BpmnScriptEngine
|
||||
from SpiffWorkflow.bpmn.parser.task_parsers import UserTaskParser
|
||||
from SpiffWorkflow.bpmn.parser.util import full_tag
|
||||
from SpiffWorkflow.bpmn.serializer.BpmnSerializer import BpmnSerializer
|
||||
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
|
||||
from SpiffWorkflow.camunda.parser.CamundaParser import CamundaParser
|
||||
|
||||
from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser
|
||||
|
||||
from crc import session
|
||||
from crc.models.file import FileDataModel, FileModel
|
||||
from crc.models.file import FileDataModel, FileModel, FileType
|
||||
from crc.models.workflow import WorkflowStatus
|
||||
|
||||
|
||||
|
@ -31,6 +33,14 @@ class CustomBpmnScriptEngine(BpmnScriptEngine):
|
|||
klass().do_task(task.data)
|
||||
|
||||
|
||||
class MyCustomParser(BpmnDmnParser):
|
||||
"""
|
||||
A BPMN and DMN parser that can also parse Camunda forms.
|
||||
"""
|
||||
OVERRIDE_PARSER_CLASSES = BpmnDmnParser.OVERRIDE_PARSER_CLASSES
|
||||
OVERRIDE_PARSER_CLASSES.update(CamundaParser.OVERRIDE_PARSER_CLASSES)
|
||||
|
||||
|
||||
class WorkflowProcessor:
|
||||
_script_engine = CustomBpmnScriptEngine()
|
||||
_serializer = BpmnSerializer()
|
||||
|
@ -40,23 +50,34 @@ class WorkflowProcessor:
|
|||
self.bpmn_workflow = self._serializer.deserialize_workflow(bpmn_json, workflow_spec=wf_spec)
|
||||
self.bpmn_workflow.script_engine = self._script_engine
|
||||
|
||||
@staticmethod
|
||||
def get_parser():
|
||||
parser = MyCustomParser()
|
||||
return parser
|
||||
|
||||
@staticmethod
|
||||
def get_spec(workflow_spec_id):
|
||||
parser = WorkflowProcessor.get_parser()
|
||||
process_id = None
|
||||
file_data_models = session.query(FileDataModel) \
|
||||
.join(FileModel) \
|
||||
.filter(FileModel.workflow_spec_id == workflow_spec_id).all()
|
||||
parser = CamundaParser()
|
||||
for file_data in file_data_models:
|
||||
if file_data.file_model.type == FileType.bpmn:
|
||||
bpmn: ElementTree.Element = ElementTree.fromstring(file_data.data)
|
||||
if file_data.file_model.primary:
|
||||
process_id = WorkflowProcessor.__get_process_id(bpmn)
|
||||
parser.add_bpmn_xml(bpmn, filename=file_data.file_model.name)
|
||||
elif file_data.file_model.type == FileType.dmn:
|
||||
dmn: ElementTree.Element = ElementTree.fromstring(file_data.data)
|
||||
parser.add_dmn_xml(dmn, filename=file_data.file_model.name)
|
||||
if process_id is None:
|
||||
raise(Exception("There is no primary BPMN model defined for workflow " + workflow_spec_id))
|
||||
return parser.get_spec(process_id)
|
||||
|
||||
@classmethod
|
||||
def create(cls, workflow_spec_id):
|
||||
parser = CamundaParser()
|
||||
spec = WorkflowProcessor.get_spec(workflow_spec_id)
|
||||
|
||||
bpmn_workflow = BpmnWorkflow(spec, script_engine=cls._script_engine)
|
||||
bpmn_workflow.do_engine_steps()
|
||||
json = cls._serializer.serialize_workflow(bpmn_workflow)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import datetime
|
||||
import glob
|
||||
import os
|
||||
|
||||
from crc import app, db, session
|
||||
|
@ -36,26 +37,53 @@ class ExampleDataLoader:
|
|||
description='Displays a random fact about a topic of your choosing.')
|
||||
workflow_specifications += \
|
||||
self.create_spec(id="two_forms",
|
||||
display_name="Two dump questions on two seperate tasks",
|
||||
description='Displays a random fact about a topic of your choosing.')
|
||||
display_name="Two dump questions on two separate tasks",
|
||||
description='the name says it all')
|
||||
workflow_specifications += \
|
||||
self.create_spec(id="decision_table",
|
||||
display_name="Form with Decision Table",
|
||||
description='the name says it all')
|
||||
|
||||
|
||||
all_data = studies + workflow_specifications
|
||||
return all_data
|
||||
|
||||
def create_spec(self, id, display_name, description):
|
||||
"""Assumes that a file exists in static/bpmn with the same name as the given id.
|
||||
"""Assumes that a directory exists in static/bpmn with the same name as the given id.
|
||||
further assumes that the [id].bpmn is the primary file for the workflow.
|
||||
returns an array of data models to be added to the database."""
|
||||
models = []
|
||||
spec = WorkflowSpecModel(id=id,
|
||||
display_name=display_name,
|
||||
description=description)
|
||||
file_model = FileModel(name=id + ".bpmn", type=FileType.bpmn, version="1",
|
||||
last_updated=datetime.datetime.now(), primary=True,
|
||||
models.append(spec)
|
||||
|
||||
filepath = os.path.join(app.root_path, 'static', 'bpmn', id, "*")
|
||||
files = glob.glob(filepath)
|
||||
for file_path in files:
|
||||
noise, file_extension = os.path.splitext(file_path)
|
||||
filename = os.path.basename(file_path)
|
||||
if file_extension.lower() == '.bpmn':
|
||||
type=FileType.bpmn
|
||||
elif file_extension.lower() == '.dmn':
|
||||
type=FileType.dmn
|
||||
elif file_extension.lower() == '.svg':
|
||||
type = FileType.svg
|
||||
else:
|
||||
raise Exception("Unsupported file type:" + file_path)
|
||||
continue
|
||||
|
||||
is_primary = filename.lower() == id + ".bpmn"
|
||||
file_model = FileModel(name=filename, type=type, version="1",
|
||||
last_updated=datetime.datetime.now(), primary=is_primary,
|
||||
workflow_spec_id=id)
|
||||
filename = os.path.join(app.root_path, 'static', 'bpmn', id + ".bpmn")
|
||||
file = open(filename, "rb")
|
||||
workflow_data = FileDataModel(data=file.read(), file_model=file_model)
|
||||
models.append(file_model)
|
||||
try:
|
||||
file = open(file_path, "rb")
|
||||
models.append(FileDataModel(data=file.read(), file_model=file_model))
|
||||
finally:
|
||||
file.close()
|
||||
return [spec, file_model, workflow_data]
|
||||
return models
|
||||
|
||||
@staticmethod
|
||||
def clean_db():
|
||||
|
|
|
@ -198,3 +198,4 @@ class TestStudy(BaseTest, unittest.TestCase):
|
|||
json_data = json.loads(rv.get_data(as_text=True))
|
||||
tasks = TaskSchema(many=True).load(json_data)
|
||||
self.assertEqual("StepTwo", tasks[0].name)
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import unittest
|
||||
|
||||
from crc import session
|
||||
from crc.models.file import FileModel
|
||||
from crc.models.workflow import WorkflowSpecModel, WorkflowStatus
|
||||
from crc.workflow_processor import WorkflowProcessor
|
||||
from tests.base_test import BaseTest
|
||||
|
@ -32,11 +33,24 @@ class TestWorkflowProcessor(BaseTest, unittest.TestCase):
|
|||
self.assertIsNotNone(data)
|
||||
self.assertIn("details", data)
|
||||
|
||||
def test_two_forms(self):
|
||||
def test_workflow_with_dmn(self):
|
||||
self.load_example_data()
|
||||
workflow_spec_model = session.query(WorkflowSpecModel).filter_by(id="two_forms").first()
|
||||
files = session.query(FileModel).filter_by(workflow_spec_id='decision_table').all()
|
||||
self.assertEquals(2, len(files))
|
||||
workflow_spec_model = session.query(WorkflowSpecModel).filter_by(id="decision_table").first()
|
||||
processor = WorkflowProcessor.create(workflow_spec_model.id)
|
||||
self.assertEqual(WorkflowStatus.user_input_required, processor.get_status())
|
||||
next_user_tasks = processor.next_user_tasks()
|
||||
self.assertEqual(1, len(next_user_tasks))
|
||||
task = next_user_tasks[0]
|
||||
self.assertEqual("get_num_presents", task.get_name())
|
||||
model = {"num_presents": 1}
|
||||
if task.data is None:
|
||||
task.data = {}
|
||||
task.data.update(model)
|
||||
processor.complete_task(task)
|
||||
processor.do_engine_steps()
|
||||
data = processor.get_data()
|
||||
self.assertIsNotNone(data)
|
||||
self.assertIn("message", data)
|
||||
self.assertEqual("Oh, Ginger.", data.get('message'))
|
Loading…
Reference in New Issue