Skip to content

Commit

Permalink
Merge pull request #39 from chrisns/master
Browse files Browse the repository at this point in the history
graphviz visualiser
  • Loading branch information
knipknap committed Oct 17, 2015
2 parents fd71a92 + 142ad49 commit 9cc291e
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 14 deletions.
2 changes: 1 addition & 1 deletion SpiffWorkflow/bpmn/storage/CompactWorkflowSerializer.py
Expand Up @@ -303,7 +303,7 @@ def new_workflow(self, workflow_spec, read_only=False, **kwargs):
:param read_only: this should be in read only mode
:param kwargs: Any extra kwargs passed to the deserialize_workflow method will be passed through here
"""
return BpmnWorkflow(workflow_spec, read_only=read_only)
return BpmnWorkflow(workflow_spec, read_only=read_only, **kwargs)

def _get_workflow_state(self, workflow):
active_tasks = workflow.get_tasks(state=(Task.READY | Task.WAITING))
Expand Down
3 changes: 3 additions & 0 deletions SpiffWorkflow/storage/DictionarySerializer.py
Expand Up @@ -57,6 +57,9 @@ def _deserialize_pathattrib(self, s_state):
def _serialize_operator(self, op):
return [self._serialize_arg(a) for a in op.args]

def _deserialize_operator(self, s_state):
return [self._deserialize_arg(c) for c in s_state]

def _serialize_operator_equal(self, op):
return self._serialize_operator(op)

Expand Down
4 changes: 2 additions & 2 deletions SpiffWorkflow/storage/XmlSerializer.py
Expand Up @@ -193,9 +193,9 @@ def _deserialize_task_spec(self, workflow, start_node, read_specs):
_exc('Invalid task name "%s"' % name)
if name in read_specs:
_exc('Duplicate task name "%s"' % name)
if cancel != '' and cancel != u'0':
if cancel != '' and cancel != '0':
kwargs['cancel'] = True
if success != '' and success != u'0':
if success != '' and success != '0':
kwargs['success'] = True
if times != '':
kwargs['times'] = int(times)
Expand Down
1 change: 1 addition & 0 deletions SpiffWorkflow/storage/__init__.py
Expand Up @@ -4,6 +4,7 @@
from .XmlSerializer import XmlSerializer
from .DictionarySerializer import DictionarySerializer
from .JSONSerializer import JSONSerializer
from .dotVisualizer import dotVisualizer

import inspect
__all__ = [name for name, obj in list(locals().items())
Expand Down
76 changes: 76 additions & 0 deletions SpiffWorkflow/storage/dotVisualizer.py
@@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
from __future__ import division
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA


# requires: https://github.com/stricaud/gvgen
import gvgen

from SpiffWorkflow.storage.Serializer import Serializer

class dotVisualizer(Serializer):
def serialize_workflow_spec(self, wf_spec):
nodes = set()
linked = set()
graph = gvgen.GvGen()
parent = graph.newItem("Workflow")

# these built in shapes are available: http://www.graphviz.org/doc/info/shapes.html
graph.styleAppend("Cancel", "shape", "oval")
graph.styleAppend("CancelTask", "shape", "oval")
graph.styleAppend("Choose", "shape", "diamond")
graph.styleAppend("ExclusiveChoice", "shape", "diamond")
graph.styleAppend("Execute", "shape", "rect")
graph.styleAppend("Gate", "shape", "trapezium")
graph.styleAppend("Join", "shape", "invtriangle")
graph.styleAppend("Merge", "shape", "invtriangle")
graph.styleAppend("MultiChoice", "shape", "diamond")
graph.styleAppend("MultiInstance", "shape", "box")
graph.styleAppend("ReleaseMutex", "shape", "diamond")
graph.styleAppend("Simple", "shape", "rect")
graph.styleAppend("StartTask", "shape", "oval")
graph.styleAppend("SubWorkflow", "shape", "invhouse")
graph.styleAppend("ThreadMerge", "shape", "invtriangle")
graph.styleAppend("ThreadSplit", "shape", "triangle")
graph.styleAppend("ThreadStart", "shape", "oval")
graph.styleAppend("Transform", "shape", "rect")
graph.styleAppend("Trigger", "shape", "oval")

# build graph with all the nodes first
def recurisvelyAddNodes(task_spec):
if task_spec in nodes:
return
task_spec.gv = graph.newItem(task_spec.name, parent)
# add a default style for this class so that if we don't have one when we apply it doesn't break the GvGen library
graph.styleAppend(task_spec.__class__.__name__, "ignore", "this")
graph.styleApply(task_spec.__class__.__name__, task_spec.gv)
nodes.add(task_spec)
sub_specs = ([task_spec.spec.start] if hasattr(task_spec, 'spec') else []) + task_spec.outputs
for t in sub_specs:
recurisvelyAddNodes(t)

# then link all the nodes together
def recursive_linking(task_spec):
if task_spec in linked:
return
linked.add(task_spec)
sub_specs = ([task_spec.spec.start] if hasattr(task_spec, 'spec') else []) + task_spec.outputs
for i, t in enumerate(sub_specs):
graph.newLink(task_spec.gv, t.gv)
recursive_linking(t)

recurisvelyAddNodes(wf_spec.start)
recursive_linking(wf_spec.start)
return (graph.dot() if graph.dot() else '')
22 changes: 11 additions & 11 deletions tests/SpiffWorkflow/specs/CeleryTest.py
Expand Up @@ -59,19 +59,19 @@ def testSerializationWithoutKwargs(self):
self.assertIsInstance(kw_defined2.kwargs['some_ref'], Attrib)


args = [b64encode(pickle.dumps(v)) for v in [Attrib('the_attribute'), u'ip', u'dc455016e2e04a469c01a866f11c0854']]
args = [b64encode(pickle.dumps(v)) for v in [Attrib('the_attribute'), 'ip', 'dc455016e2e04a469c01a866f11c0854']]

data = { u'R': b64encode(pickle.dumps(u'1'))}
data = { 'R': b64encode(pickle.dumps('1'))}
# Comes from live data. Bug not identified, but there we are...
data = {u'inputs': [u'Wait:1'], u'lookahead': 2, u'description': u'',
u'outputs': [], u'args': args,
u'manual': False,
u'data': data, u'locks': [], u'pre_assign': [],
u'call': u'call.x',
u'internal': False, u'post_assign': [], u'id': 8,
u'result_key': None, u'defines': data,
u'class': u'SpiffWorkflow.specs.Celery.Celery',
u'name': u'RS1:1'}
data = {'inputs': ['Wait:1'], 'lookahead': 2, 'description': '',
'outputs': [], 'args': args,
'manual': False,
'data': data, 'locks': [], 'pre_assign': [],
'call': 'call.x',
'internal': False, 'post_assign': [], 'id': 8,
'result_key': None, 'defines': data,
'class': 'SpiffWorkflow.specs.Celery.Celery',
'name': 'RS1:1'}
Celery.deserialize(serializer, new_wf_spec, data)

def suite():
Expand Down

0 comments on commit 9cc291e

Please sign in to comment.