瀏覽代碼

WIP: PT renderer backend endpoint

Joeri Exelmans 2 年之前
父節點
當前提交
8f4e471291
共有 9 個文件被更改,包括 129 次插入47 次删除
  1. 8 5
      README.md
  2. 9 8
      backend/README.md
  3. 33 6
      backend/dtdesign-backend
  4. 63 0
      drawio2oml/pt/fetcher.py
  5. 3 2
      drawio2oml/pt/renderer.py
  6. 4 4
      flake.lock
  7. 2 1
      flake.nix
  8. 1 0
      setup.py
  9. 6 21
      test/run_tests.py

+ 8 - 5
README.md

@@ -23,11 +23,14 @@ The Python package `drawio2oml` parses .drawio files as instances of DSLs and co
 Even when invoking a non-drawio exporter, the diagram will also be exported as a drawio instance, as well as a "correspondence" instance, containing traceability links between the drawio-instance and the non-drawio instance.
 
 ## Dependencies
-
-  - drawio2py (for drawio's "abstract syntax" definitions)
-    - https://msdl.uantwerpen.be/git/jexelmans/drawio2py
-  - jinja2 (for template-based OML generation)
-    - https://pypi.org/project/Jinja2/
+  
+  - Python packages:
+    - drawio2py (for drawio's "abstract syntax" definitions)
+      - https://msdl.uantwerpen.be/git/jexelmans/drawio2py
+    - jinja2 (for template-based OML generation)
+      - https://pypi.org/project/Jinja2/
+    - Requests (HTTP client, to fetch process traces from 'wee')
+      - https://pypi.org/project/requests/
 
 ## Running
 

+ 9 - 8
backend/README.md

@@ -13,14 +13,15 @@ The plugin uses the backend to:
 
 On top of the dependencies listed in this repo's main [README](../README.md), you also need:
 
-  - drawio2oml
-    - (see parent directory)
-  - xopp2py (Xournal++ to Python to OML converter)
-    - https://msdl.uantwerpen.be/git/jexelmans/xopp2py
-  - Flask (HTTP web server)
-    - https://pypi.org/project/Flask/
-  - Flask-Cors (Flask extension to easily set all HTTP response headers to allow CORS)
-    - https://pypi.org/project/Flask-Cors/
+  - Python packages
+    - drawio2oml
+      - (see parent directory)
+    - xopp2py (Xournal++ to Python to OML converter)
+      - https://msdl.uantwerpen.be/git/jexelmans/xopp2py
+    - Flask (HTTP web server)
+      - https://pypi.org/project/Flask/
+    - Flask-Cors (Flask extension to easily set all HTTP response headers to allow CORS)
+      - https://pypi.org/project/Flask-Cors/
   - A local clone of this OML project directory:
     - https://msdl.uantwerpen.be/git/lucasalbertins/DTDesign/src/joeri/examples/oml/SystemDesignOntology2Layers
     - We'll refer to the path of your local clone as `PATH/TO/SDO2L`.

+ 33 - 6
backend/dtdesign-backend

@@ -17,23 +17,41 @@ import argparse
 
 # packages from pypi:
 import flask
-from flask_cors import CORS
+import flask_cors
 
 # our own packages:
 from drawio2py.parser import Parser as drawio_parser
+from drawio2py.generator import generate_diagram
+import drawio2py.util
 from drawio2oml.convert import page_to_oml
 from drawio2oml.bundle.oml_generator import write_bundle
-from drawio2oml import util 
+from drawio2oml import util
 from xopp2py import parser as xopp_parser
 from xopp2oml import writer as xopp_oml_writer
 
+from drawio2oml.pt import fetcher as pt_fetcher
+from drawio2oml.pt import renderer as pt_renderer
+
 argparser = argparse.ArgumentParser(
     description = "Backend for DTDesign drawio plugin.")
 argparser.add_argument('projectdir', help="Path to SystemDesignOntology2Layers OML project directory (containing \"build.gradle\"). Example: /home/maestro/repos/dtdesign/examples/oml/SystemDesignOntology2Layers")
+argparser.add_argument('shapelibdir', help="Path to where the Draw.io shape libraries (bunch of XML files) for FTG, PM and PT are at. Necessary for rendering process traces. Example: /home/maestro/repos/drawio/src/main/webapp/myPlugins/shape_libs")
 args = argparser.parse_args() # exits on error
 
+WEE = "http://localhost:8081"
+WEE_FETCHER = pt_fetcher.Fetcher(WEE)
+
 # See below
 DTDESIGN_PROJECT_DIR = args.projectdir
+SHAPE_LIB_DIR = args.shapelibdir
+
+# Quick check to see if the projectdir argument is valid:
+if not os.path.isfile(DTDESIGN_PROJECT_DIR + "/build.gradle"):
+    sys.exit("Invalid projectdir: " + DTDESIGN_PROJECT_DIR)
+
+# Quick check to see if the shapelibdir argument is valid:
+if not os.path.isfile(SHAPE_LIB_DIR + "/pt.xml"):
+    sys.exit("Invalid shapelibdir: " + SHAPE_LIB_DIR)
 
 OML_DESCRIPTION_DIR = DTDESIGN_PROJECT_DIR + "/src/oml/ua.be/sdo2l/description"
 OML_ARTIFACT_DIR = OML_DESCRIPTION_DIR + "/artifacts"
@@ -44,9 +62,6 @@ XOPP_DIR = FILES_DIR + "/xopp"
 CSV_DIR = FILES_DIR + "/csv"
 ANYFILE_DIR = FILES_DIR + "/file"
 
-# Quick check to see if the projectdir argument is correct:
-if not os.path.isfile(DTDESIGN_PROJECT_DIR + "/build.gradle"):
-    sys.exit("Invalid projectdir: " + DTDESIGN_PROJECT_DIR)
 
 print()
 print(" ",DTDESIGN_PROJECT_DIR)
@@ -76,7 +91,7 @@ for d in [DRAWIO_DIR, XOPP_DIR, CSV_DIR, ANYFILE_DIR]:
     os.makedirs(d, exist_ok=True)
 
 app = flask.Flask(__name__)
-CORS(app)
+flask_cors.CORS(app)
 
 # API version
 @app.route("/version", methods=["GET"])
@@ -110,6 +125,17 @@ def put_drawio_file(model_name):
         f.write(diagram)
     return json.dumps(parsed_as), 200 # OK
 
+# Even though the slashes in pt_iri will be escaped, we still have to tell Flask it's a 'path' or it won't accept it.
+@app.route("/render_pt/<path:pt_iri>", methods=["GET"])
+def render_pt(pt_iri):
+    last_event = WEE_FETCHER.get_pt(pt_iri)
+    id_gen = drawio2py.util.DrawioIDGenerator()
+    page = drawio2py.util.generate_empty_page(id_gen, "XXX")
+    default_layer = page.root.children[0]
+    pt_renderer.render(SHAPE_LIB_DIR, last_event, parent=default_layer, id_gen=id_gen)
+    diagram_node = generate_diagram(page)
+    return ET.tostring(diagram_node, encoding="unicode"), 200 # OK
+
 @app.route("/files/xopp/<file_name>", methods=["PUT"])
 def put_xopp_file(file_name):
     if file_name[-5:] != ".xopp":
@@ -151,6 +177,7 @@ def put_any_file(model_name):
         f.write(file_data)
     return json.dumps(["file"]), 200 # OK
 
+# TODO: this endpoint should queue rebuilds (using Futures?), such that only one rebuild is running at a time.
 @app.route("/rebuild_oml", methods=["PUT"])
 def rebuild_oml():
     with open(OML_DESCRIPTION_DIR+'/bundle.oml', 'wt') as f:

+ 63 - 0
drawio2oml/pt/fetcher.py

@@ -0,0 +1,63 @@
+import urllib
+import requests
+
+import pprint
+
+from drawio2oml.pt import abstract_syntax as pt_as
+from drawio2oml.util import DTDESIGN_NAMESPACES
+
+def strip_iri(iri):
+    # Remove artifacts-prefix from iri:
+    return iri[len(DTDESIGN_NAMESPACES["artifacts"]):]
+
+# For a given StartTraceEvent iri, gets all the events from the trace (from WEE), and produces the Python in-memory abstract syntax for it:
+class Fetcher:
+    def __init__(self, wee_host: str):
+        self.wee_host = wee_host
+
+    def get_pt(self, pt_iri: str) -> pt_as.StartTraceEvent:
+        r = requests.get(self.wee_host + "/traces/events/" + urllib.parse.quote(pt_iri, safe=''))
+        json = r.json()
+        pprint.pprint(json)
+
+        # iri_to_as_event = {}
+
+        def consumed_produced_artifacts(ls):
+            # TODO
+            return []
+
+        last_event = None
+        for event in json:
+            if event['type'] == "StartTraceEvent":
+                as_event = pt_as.StartTraceEvent(
+                    after=last_event,
+                    timestamp=event['timestamp'],
+                    pm=strip_iri(event['pmEnacted']['iri']),
+                )
+            elif event['type'] == "StartActivityEvent":
+                as_event = pt_as.StartActivityEvent(
+                    after=last_event,
+                    timestamp=event['timestamp'],
+                    activity=strip_iri(event['relatesTo']['activity']['iri']),
+                    port=strip_iri(event['relatesTo']['iri']),
+                    consumes=consumed_produced_artifacts(event['consumedArtifacts']),
+                )
+            elif event['type'] == "EndActivityEvent":
+                as_event = pt_as.EndActivityEvent(
+                    after=last_event,
+                    timestamp=event['timestamp'],
+                    activity=strip_iri(event['relatesTo']['activity']['iri']),
+                    port=strip_iri(event['relatesTo']['iri']),
+                    consumes=consumed_produced_artifacts(event['producedArtifacts']),
+                )
+            elif event['type'] == "EndTraceEvent":
+                as_event = pt_as.EndTraceEvent(
+                    after=last_event,
+                    timestamp=event['timestamp'],
+                )
+            else:
+                raise Exception("Got unknown event type from WEE: " + event['type'])
+            last_event = as_event
+            # iri_to_as_event[event['iri']] = as_event
+
+        return last_event

+ 3 - 2
drawio2oml/pt/renderer.py

@@ -6,6 +6,7 @@ from drawio2oml.pt import abstract_syntax as pt_as
 from drawio2py import abstract_syntax as dio_as
 from drawio2py import parser as dio_parser
 from drawio2py import shapelib
+from drawio2py import util
 
 class VertexPositionGenerator:
     """
@@ -33,7 +34,7 @@ class VertexPositionGenerator:
         self.artifact_pos_y += 100
         return (Decimal(self.artifact_pos_x), Decimal(y))
 
-def render(shapelib_dir: str, last_event: pt_as.Event, parent: dio_as.Cell):
+def render(shapelib_dir: str, last_event: pt_as.Event, parent: dio_as.Cell, id_gen: util.DrawioIDGenerator):
     """
     Renders a process trace to drawio.
 
@@ -48,7 +49,7 @@ def render(shapelib_dir: str, last_event: pt_as.Event, parent: dio_as.Cell):
     common_library = shapelib.parse_library(os.path.join(shapelib_dir,"common.xml"))
     pm_library = shapelib.parse_library(os.path.join(shapelib_dir,"pm.xml"))
 
-    cloner = shapelib.ShapeCloner(id_gen=shapelib.DrawioIDGenerator())
+    cloner = shapelib.ShapeCloner(id_gen=id_gen)
 
     def set_property(cell, key, value):
         if key in cell.properties:

+ 4 - 4
flake.lock

@@ -25,11 +25,11 @@
         "nixpkgs": "nixpkgs_2"
       },
       "locked": {
-        "lastModified": 1682162175,
-        "narHash": "sha256-XGC56eC+JLioGgWOaVpk7qTH1rHVFJwHwo77dSCfki8=",
+        "lastModified": 1682606943,
+        "narHash": "sha256-4fKE6AuPuVF/u8xIQDMPRAErTj7DmeYBKyGbbc37Z+8=",
         "ref": "library",
-        "rev": "0ee49cb56803feb9919c81740d3b1771cc37e336",
-        "revCount": 62,
+        "rev": "4b94fb45b1f39052c8b99fb868a023009303ae50",
+        "revCount": 64,
         "type": "git",
         "url": "ssh://git@msdl.uantwerpen.be/jexelmans/drawio2py.git"
       },

+ 2 - 1
flake.nix

@@ -29,8 +29,9 @@
                 version = "1.0.0";
                 src = ./.; # location of setup.py
                 propagatedBuildInputs = [
-                  pkgs.python3Packages.jinja2
                   myLibs.drawio2py
+                  pkgs.python3Packages.jinja2
+                  pkgs.python3Packages.requests
                 ];
                 checkPhase = ''
                   runHook preCheck

+ 1 - 0
setup.py

@@ -6,6 +6,7 @@ setup(
 
     install_requires=[
         "jinja2",
+        "requests",
     ],
 
     package_dir={"":"."},

+ 6 - 21
test/run_tests.py

@@ -7,6 +7,7 @@ import unittest
 from drawio2py import abstract_syntax as dio_as
 from drawio2py import parser as dio_parser
 from drawio2py import generator as dio_generator
+from drawio2py import util
 from drawio2oml.drawio import oml_generator as dio_oml_generator
 from drawio2oml.pm import parser as pm_parser
 from drawio2oml.pm import oml_generator as pm_oml_generator
@@ -120,6 +121,7 @@ class Tests(unittest.TestCase):
     def test_pm3(self):
         run_pm_test("pm/MyFancyPM.drawio")
 
+    # In order to test rendering of Process Traces, we need the path of the shape_libs dir (from drawio repo). Tests don't have command line arguments, so let's get it from an environment variable:
     @unittest.skipIf("DRAWIOSHAPELIBS" not in os.environ, "DRAWIOSHAPELIBS environment variable not set")
     def test_render_pt(self):
 
@@ -148,28 +150,11 @@ class Tests(unittest.TestCase):
             timestamp="2011-06-01T19:16:58.869Z",
             after=e2)
 
-        root = dio_as.Cell(
-            id="0",
-            value="",
-            parent=None,
-            children=[],
-            properties={},
-            style=None,
-            attributes={},
-        )
-        layer = dio_as.Cell(
-            id="1",
-            value="",
-            parent=root,
-            children=[],
-            properties={},
-            style=None,
-            attributes={},
-        )
-        root.children.append(layer)
+        id_gen = util.DrawioIDGenerator()
+        page = util.generate_empty_page(id_gen, "Generated PT")
+        default_layer = page.root.children[0]
         shapelib_dir = os.environ["DRAWIOSHAPELIBS"]
-        pt_renderer.render(shapelib_dir=shapelib_dir, last_event=e3, parent=layer)
-        page = dio_as.Page(id="meh", name="Generated PT", attributes={}, root=root)
+        pt_renderer.render(shapelib_dir=shapelib_dir, last_event=e3, parent=default_layer, id_gen=id_gen)
         file = dio_as.DrawIOFile(filename="XXX", compressed=False, version="20", pages=[page])
 
         # import pprint