From e379171fe1f996a0dca41895a2dd1004d85a21b4 Mon Sep 17 00:00:00 2001 From: Martin Lehmann Date: Wed, 10 Apr 2024 15:26:32 +0200 Subject: [PATCH 1/5] feat: Add a generic fallback template --- templates/__generic__.html.j2 | 20 ++++++++++++++++++++ templates/__generic__.yaml | 10 ++++++++++ 2 files changed, 30 insertions(+) create mode 100644 templates/__generic__.html.j2 create mode 100644 templates/__generic__.yaml diff --git a/templates/__generic__.html.j2 b/templates/__generic__.html.j2 new file mode 100644 index 0000000..1681de3 --- /dev/null +++ b/templates/__generic__.html.j2 @@ -0,0 +1,20 @@ +{# + Copyright DB InfraGO AG and contributors + SPDX-License-Identifier: Apache-2.0 +#} +{% if object.name %} +

{{ object.name }} ({{ object.__class__.__name__ }})

+{% else %} +

Unnamed {{ object.__class__.__name__ }})

+{% endif %} +{%- for attr in object.__dir__()|sort if not attr.startswith("_") and attr not in ("from_model", "pvmt") -%} + {%- set value = object[attr] -%} +

{{ attr }}

+ {%- if value.as_svg -%} + {{ value.as_svg|safe }} + {% elif value._short_html_ -%} +

{{ value._short_html_() }}

+ {% else -%} +

{{ value }}

+ {% endif -%} +{% endfor -%} diff --git a/templates/__generic__.yaml b/templates/__generic__.yaml new file mode 100644 index 0000000..e5e7c41 --- /dev/null +++ b/templates/__generic__.yaml @@ -0,0 +1,10 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +idx: __generic__ +template: __generic__.html.j2 +name: Generic object display +description: Generic built-in template that shows all available information about a given object. +category: other +variable: + name: object From 3c415f3d31d488f2c3cd788071ff604a4df80059 Mon Sep 17 00:00:00 2001 From: Martin Lehmann Date: Wed, 10 Apr 2024 15:33:46 +0200 Subject: [PATCH 2/5] feat: Enable easier cross-template links This adds a finalizer and a filter to Jinja, which together allow for easily making cross-template links. The new `make_href` filter takes a model element as target and generates the URL for inserting into e.g. the `href` attribute of anchors. Example: ``` {% set obj = model.by_uuid(...) %} {% set target = obj.parent %} Go to parent ``` --- capella_model_explorer/backend/explorer.py | 22 ++++++++++++++++++++++ templates/classes.html.j2 | 2 +- templates/diagrams.html.j2 | 2 +- templates/system-capability.html.j2 | 2 +- templates/system_function.html.j2 | 4 ++-- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/capella_model_explorer/backend/explorer.py b/capella_model_explorer/backend/explorer.py index 91d3557..06515a6 100644 --- a/capella_model_explorer/backend/explorer.py +++ b/capella_model_explorer/backend/explorer.py @@ -10,6 +10,7 @@ from pathlib import Path import capellambse +import markupsafe import yaml from fastapi import APIRouter, FastAPI, Request from fastapi.middleware.cors import CORSMiddleware @@ -44,6 +45,8 @@ def __post_init__(self): allow_headers=["*"], ) self.env = Environment() + self.env.finalize = self.__finalize + self.env.filters["make_href"] = self.__make_href self.grouped_templates, self.templates = index_templates( self.templates_path ) @@ -51,6 +54,25 @@ def __post_init__(self): self.configure_routes() self.app.include_router(self.router) + def __finalize(self, markup: t.Any) -> object: + markup = markupsafe.escape(markup) + return capellambse.helpers.replace_hlinks( + markup, self.model, self.__make_href + ) + + def __make_href(self, obj: capellambse.model.GenericElement) -> str | None: + if isinstance(obj, capellambse.model.ElementList): + raise TypeError("Cannot make an href to a list of elements") + if not isinstance(obj, capellambse.model.GenericElement): + raise TypeError(f"Expected a model object, got {obj!r}") + + for idx, template in self.templates.items(): + clsname = template.get("variable", {}).get("type") + if obj.__class__.__name__ == clsname: + return f"/{idx}/{obj.uuid}" + + return f"/__generic__/{obj.uuid}" + def render_instance_page(self, template_text, object=None): try: # render the template with the object diff --git a/templates/classes.html.j2 b/templates/classes.html.j2 index 78af19b..3ba62da 100644 --- a/templates/classes.html.j2 +++ b/templates/classes.html.j2 @@ -37,7 +37,7 @@ {{ property.name }} {% if property.type.__class__ == object.__class__ %} - {{ property.type.name }} + {{ property.type.name }} {% else %} {{ property.type.name }} {% endif %} diff --git a/templates/diagrams.html.j2 b/templates/diagrams.html.j2 index fc21094..1c5c569 100644 --- a/templates/diagrams.html.j2 +++ b/templates/diagrams.html.j2 @@ -18,7 +18,7 @@ diff --git a/templates/system-capability.html.j2 b/templates/system-capability.html.j2 index 22edad6..709777b 100644 --- a/templates/system-capability.html.j2 +++ b/templates/system-capability.html.j2 @@ -49,7 +49,7 @@ diff --git a/templates/system_function.html.j2 b/templates/system_function.html.j2 index 8890dcb..df896b2 100644 --- a/templates/system_function.html.j2 +++ b/templates/system_function.html.j2 @@ -13,7 +13,7 @@

Function owner

{% if object.owner %} -

An Entity that is responsible for providing this function: {{object.owner.name}}.

+

An Entity that is responsible for providing this function: {{object.owner.name}}.

{% else %}

This function is not allocated to any entity / no entity is responsible for it.

{% endif %} @@ -23,7 +23,7 @@

The function is available in the following Entity states:

{% else %} From 5d27b01189429a487a69da90089c290a68f56fc6 Mon Sep 17 00:00:00 2001 From: Martin Lehmann Date: Thu, 11 Apr 2024 10:07:35 +0200 Subject: [PATCH 3/5] fix: Use correct instance check for element lists --- capella_model_explorer/backend/explorer.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/capella_model_explorer/backend/explorer.py b/capella_model_explorer/backend/explorer.py index 06515a6..340382f 100644 --- a/capella_model_explorer/backend/explorer.py +++ b/capella_model_explorer/backend/explorer.py @@ -225,8 +225,13 @@ def find_objects(model, obj_type=None, below=None, attr=None, filters=None): if attr: getter = operator.attrgetter(attr) objects = getter(model) - if objects and not isinstance(objects, list): + if hasattr(objects, "_element"): objects = [objects] + elif not isinstance(objects, capellambse.model.ElementList): + raise ValueError( + f"Expected a list of model objects or a single model object" + f" for {attr!r} of the model, got {objects!r}" + ) elif below and obj_type: getter = operator.attrgetter(below) objects = model.search(obj_type, below=getter(model)) From 7a179026751f0fc94e76bbf729e5492355ae19c5 Mon Sep 17 00:00:00 2001 From: Martin Lehmann Date: Thu, 11 Apr 2024 10:13:43 +0200 Subject: [PATCH 4/5] fix: Don't auto-escape SVG content in diagram template --- templates/diagrams.html.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/diagrams.html.j2 b/templates/diagrams.html.j2 index 1c5c569..69780fa 100644 --- a/templates/diagrams.html.j2 +++ b/templates/diagrams.html.j2 @@ -4,7 +4,7 @@ #}

{{ object.name }}

-{{ object.as_svg }} +{{ object.as_svg | safe }}

Description

{% if object.description %} From 7c418f5668843c228a616ebbb9c1456e6b830403 Mon Sep 17 00:00:00 2001 From: Martin Lehmann Date: Thu, 11 Apr 2024 10:13:55 +0200 Subject: [PATCH 5/5] feat: Log exceptions while finding elements Instead of only reporting the error to the frontend, also log the exception and its stack trace on the console. This greatly helps with debugging. --- capella_model_explorer/backend/explorer.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/capella_model_explorer/backend/explorer.py b/capella_model_explorer/backend/explorer.py index 340382f..23cea99 100644 --- a/capella_model_explorer/backend/explorer.py +++ b/capella_model_explorer/backend/explorer.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 import dataclasses +import logging import operator import os import pathlib @@ -21,6 +22,7 @@ PATH_TO_FRONTEND = Path("./frontend/dist") ROUTE_PREFIX = os.getenv("ROUTE_PREFIX", "") +LOGGER = logging.getLogger(__name__) @dataclasses.dataclass @@ -144,6 +146,9 @@ def read_template(template_name: str): {"idx": obj.uuid, "name": obj.name} for obj in objects ] except Exception as e: + LOGGER.exception( + "Error finding objects for template %s", template_name + ) base["objects"] = [] base["error"] = str(e) return base