sartography/SpiffWorkflow

[Question] Support for groups?

shazz opened this issue · 9 comments

shazz commented

Hi,
I was looking at the NodeParser code (1.2.1) and it looks like for Tasks, Groups are not yet supported. But Positions and Lanes are.
I'd like to try to add it but maybe better to ask before :)

I guess the trick would be to get the goups postions from the XML then check the Task positions and find out which one is around ?

Thanks !

shazz commented

In the NodeParser, I tried to browse the bpmn:group elements but it looks like they are not in tree, did I miss something?

It looks like groups are a way of visually representing something called a Category, and Categories can be "used for documentation or analysis purposes", as a way of grouping a set of Flow Elements (such as tasks).

When I create a simple model in the BPMN.io editor like this one:
image

It creates a bpmn:category entity in the XML - and it's value attribute references back to the name given to the group.

  <bpmn:category id="Category_1qjs3te">
    <bpmn:categoryValue id="CategoryValue_0jddzsz" value="my_group" />
  </bpmn:category>

Out of curiosity, what you hoping to do with groups?

And the visual component is also connected to the "my_group", which will allow you to grab the coordinates.

<bpmndi:BPMNShape id="Group_0n9d0e3_di" bpmnElement="my_group">
        <dc:Bounds x="425" y="55" width="210" height="230" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="506" y="62" width="49" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
shazz commented

Thanks :) Yes this is what I'm trying to get from the NodeParser for a Task:

NodeParser

class NodeParser:

    def __init__(self, node, filename, doc_xpath, lane=None):

        self.node = node
        self.filename = filename
        self.doc_xpath = doc_xpath
        self.xpath = xpath_eval(node)
        self.lane = self._get_lane() or lane
        self.position = self._get_position() or {'x': 0.0, 'y': 0.0}
        self.group = self._get_group()


    def _get_group(self):
        shape = first(self.doc_xpath(f".//bpmndi:BPMNShape[@bpmnElement='{self.get_id()}']//dc:Bounds"))
        if shape is not None:
            node_bounds = {
                'x': float(shape.get('x', 0)),
                'y': float(shape.get('y', 0)),
                'w': float(shape.get('width', 0)),
                'h': float(shape.get('height', 0))}

        p = self.xpath('..')[0]

        children = p.getchildren()
        for child in children:
            if 'group' in child.tag:
                g_id = child.get('id')
                cref = child.get('categoryValueRef')
                cat_val = first(self.doc_xpath(f".//bpmn:categoryValue[@id='{cref}']"))
                group_name = cat_val.get('value')
                bounds = first(self.doc_xpath(f".//bpmndi:BPMNShape[@bpmnElement='{g_id}']//dc:Bounds"))
                x = float(bounds.get('x', 0))
                y = float(bounds.get('y', 0))
                w = float(bounds.get('width', 0))
                h = float(bounds.get('height', 0))

                if node_bounds['x'] > x and node_bounds['x']+node_bounds['w'] < (x+w) and node_bounds['y'] > y and node_bounds['y']+node_bounds['h'] < (y+h):
                    return group_name

        return "no group"

TaskParser

    def create_task(self):
        """
        Create an instance of the task appropriately. A subclass can override
        this method to get extra information from the node.
        """
        return self.spec_class(self.spec, self.get_task_spec_name(),
                               lane=self.lane,
                               description=self.node.get('name', None),
                               position=self.position,
                               group=self.group)

BpmnSpecMixin

class BpmnSpecMixin(TaskSpec):
    """
    All BPMN spec classes should mix this superclass in. It adds a number of
    methods that are BPMN specific to the TaskSpec.
    """

    def __init__(self, wf_spec, name, lane=None, position=None, group=None, **kwargs):
        """
        Constructor.

        :param lane: Indicates the name of the lane that this task belongs to
        (optional).
        """
        super(BpmnSpecMixin, self).__init__(wf_spec, name, **kwargs)
        self.outgoing_sequence_flows = {}
        self.outgoing_sequence_flows_by_id = {}
        self.lane = lane
        self.position = position or {'x': 0, 'y': 0}
        self.loopTask = False
        self.documentation = None
        self.data_input_associations = []
        self.data_output_associations = []
        self.group = group

(I'm sure it can be done if a better way... learning while trying :) )

shazz commented

Out of curiosity, what you hoping to do with groups?

Just that I have laaaarge BPMN/DMN processes (not very complex but fairly long) and I'm using groups just to define some "sections" of the process and when running the process I'd like to show in which section I am (kind of progress bar)

shazz commented

then I create a simple model in the BPMN.io editor

OH! I did not know this editor, I was still using the Camunda Modeler. Looks pretty nice! But I did not find a way to edit the Script Task properties (I'm using Expressions)

EDIT: ok, got it :) Interesting!

shazz commented

I created a fork and added the code to retrieve the groups. If you find it interesting: https://github.com/Grain-Ecosystem/SpiffWorkflow

Hey @shazz - sorry I let this languish, I thing there is real value in the effort - and I would like to see this functionality in SpiffWorkflow -- would you be willing to create a Pull Request on this -- with all tests passing, and new tests to assure groups are working?

shazz commented

Definitively yes :)