Skip to content

Commit

Permalink
feat: Add add_custom_diagrams serializer
Browse files Browse the repository at this point in the history
  • Loading branch information
ewuerger committed Dec 13, 2024
1 parent 494fa84 commit abf1288
Show file tree
Hide file tree
Showing 8 changed files with 406 additions and 245 deletions.
56 changes: 46 additions & 10 deletions capella2polarion/converters/converter_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
DESCRIPTION_REFERENCE_SERIALIZER = "description_reference"
DIAGRAM_ELEMENTS_SERIALIZER = "diagram_elements"

ConverterConfigDict_type: t.TypeAlias = dict[
str, t.Union[dict[str, t.Any], list[dict[str, t.Any]]]
]


@dataclasses.dataclass
class LinkConfig:
Expand Down Expand Up @@ -46,14 +50,27 @@ class CapellaTypeConfig:
"""A single Capella Type configuration."""

p_type: str | None = None
converters: str | list[str] | dict[str, dict[str, t.Any]] | None = None
converters: str | list[str] | ConverterConfigDict_type | None = None
links: list[LinkConfig] = dataclasses.field(default_factory=list)
is_actor: bool | None = None
nature: str | None = None

def __post_init__(self):
"""Post processing for the initialization."""
self.converters = _force_dict(self.converters)
self.converters = _force_dict( # type: ignore[assignment]
self.converters
)


@dataclasses.dataclass
class CustomDiagramConfig:
"""A single Capella Custom Diagram configuration."""

capella_attr: str
polarion_id: str
title: str
render_params: dict[str, t.Any] | None = None
filters: list[str] | None = None


def _default_type_conversion(c_type: str) -> str:
Expand All @@ -72,7 +89,7 @@ def __init__(self):

def read_config_file(
self,
synchronize_config: t.TextIO,
synchronize_config: str | t.TextIO,
type_prefix: str = "",
role_prefix: str = "",
):
Expand Down Expand Up @@ -301,7 +318,7 @@ def _read_capella_type_configs(


def _force_dict(
config: str | list[str] | dict[str, dict[str, t.Any]] | None,
config: str | list[str] | ConverterConfigDict_type | None,
) -> dict[str, dict[str, t.Any]]:
match config:
case None:
Expand All @@ -324,28 +341,47 @@ def add_prefix(polarion_type: str, prefix: str) -> str:


def _filter_converter_config(
config: dict[str, dict[str, t.Any]],
config: ConverterConfigDict_type,
) -> dict[str, dict[str, t.Any]]:
custom_converters = (
"include_pre_and_post_condition",
"linked_text_as_description",
"add_context_diagram",
"add_tree_diagram",
"add_realization_diagram",
"add_cable_tree_diagram",
"add_custom_diagrams",
"add_context_diagram", # TODO: Deprecated, so remove in next release
"add_tree_diagram", # TODO: Deprecated, so remove in next release
"add_jinja_fields",
"jinja_as_description",
)
filtered_config = {}
filtered_config: dict[str, dict[str, t.Any]] = {}
for name, params in config.items():
params = params or {}
if name not in custom_converters:
logger.error("Unknown converter in config %r", name)
continue

if name in ("add_context_diagram", "add_tree_diagram"):
assert isinstance(params, dict)
params = _filter_context_diagram_config(params)

if name in ("add_custom_diagrams",):
if isinstance(params, list):
params = {
"custom_diagrams_configs": [
CustomDiagramConfig(
**_filter_context_diagram_config(rp)
)
for rp in params
]
}
elif isinstance(params, dict):
assert "custom_diagrams_configs" in params
else:
logger.error( # type: ignore[unreachable]
"Unknown 'add_custom_diagrams' config %r", params
)
continue

assert isinstance(params, dict)
filtered_config[name] = params

return filtered_config
Expand Down
164 changes: 76 additions & 88 deletions capella2polarion/converters/element_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import pathlib
import re
import typing as t
import warnings
from collections import abc as cabc

import capellambse
Expand All @@ -24,7 +25,11 @@

from capella2polarion import data_model
from capella2polarion.connectors import polarion_repo
from capella2polarion.converters import data_session, polarion_html_helper
from capella2polarion.converters import (
converter_config,
data_session,
polarion_html_helper,
)

RE_DESCR_LINK_PATTERN = re.compile(
r"<a href=\"hlink://([^\"]+)\">([^<]+)<\/a>"
Expand Down Expand Up @@ -101,6 +106,7 @@ def serialize(self, uuid: str) -> data_model.CapellaWorkItem | None:
...,
data_model.CapellaWorkItem,
] = getattr(self, f"_{converter}")
assert isinstance(params, dict)
serializer(converter_data, **params)
except Exception as error:
converter_data.errors.add(
Expand Down Expand Up @@ -236,30 +242,6 @@ def __insert_diagram(

return diagram_html

def _draw_additional_attributes_diagram(
self,
work_item: data_model.CapellaWorkItem,
diagram: m.AbstractDiagram,
attribute: str,
title: str,
render_params: dict[str, t.Any] | None = None,
):
diagram_html, attachment = self._draw_diagram_svg(
diagram,
attribute,
title,
650,
"additional-attributes-diagram",
render_params,
)
if attachment:
self._add_attachment(work_item, attachment)

work_item.additional_attributes[attribute] = {
"type": "text/html",
"value": diagram_html,
}

def _sanitize_linked_text(self, obj: m.ModelElement | m.Diagram) -> tuple[
list[str],
markupsafe.Markup,
Expand Down Expand Up @@ -395,30 +377,6 @@ def _get_requirement_types_text(
type_texts[req.type.long_name].append(req.text)
return _format_texts(type_texts)

def _add_diagram(
self,
converter_data: data_session.ConverterData,
diagram_attr: str,
diagram_label: str,
render_params: dict[str, t.Any] | None = None,
filters: list[str] | None = None,
) -> data_model.CapellaWorkItem:
"""Add a new custom field diagram based on provided attributes."""
assert converter_data.work_item, "No work item set yet"
diagram = getattr(converter_data.capella_element, diagram_attr)

for filter in filters or []:
diagram.filters.add(filter)

self._draw_additional_attributes_diagram(
converter_data.work_item,
diagram,
diagram_attr,
diagram_label,
render_params,
)
return converter_data.work_item

# Serializer implementation starts below

def __generic_work_item(
Expand Down Expand Up @@ -523,56 +481,85 @@ def _linked_text_as_description(
converter_data.work_item.attachments += attachments
return converter_data.work_item

def _add_context_diagram(
def _add_custom_diagrams(
self,
converter_data: data_session.ConverterData,
render_params: dict[str, t.Any] | None = None,
filters: list[str] | None = None,
custom_diagrams_configs: list[converter_config.CustomDiagramConfig],
) -> data_model.CapellaWorkItem:
return self._add_diagram(
converter_data,
"context_diagram",
"Context Diagram",
render_params,
filters,
)
"""Add new custom field diagrams to the work item."""
assert converter_data.work_item, "No work item set yet"
for cd_config in custom_diagrams_configs:
diagram = getattr(
converter_data.capella_element, cd_config.capella_attr
)

def _add_tree_diagram(
self,
converter_data: data_session.ConverterData,
render_params: dict[str, t.Any] | None = None,
filters: list[str] | None = None,
) -> data_model.CapellaWorkItem:
return self._add_diagram(
converter_data, "tree_view", "Tree View", render_params, filters
)
for filter in cd_config.filters or []:
diagram.filters.add(filter)

def _add_realization_diagram(
diagram_html, attachment = self._draw_diagram_svg(
diagram,
cd_config.capella_attr,
cd_config.title,
650,
"additional-attributes-diagram",
cd_config.render_params,
)
if attachment:
self._add_attachment(converter_data.work_item, attachment)

converter_data.work_item.additional_attributes[
cd_config.polarion_id
] = polarion_api.HtmlContent(diagram_html)
return converter_data.work_item

def _add_context_diagram(
self,
converter_data: data_session.ConverterData,
render_params: dict[str, t.Any] | None = None,
filters: list[str] | None = None,
) -> data_model.CapellaWorkItem:
return self._add_diagram(
warnings.warn(
"Using 'add_context_diagram' is deprecated. "
"Use 'add_custom_diagrams' instead.",
DeprecationWarning,
stacklevel=2,
)
return self._add_custom_diagrams(
converter_data,
"realization_view",
"Realization Diagram",
render_params,
filters,
[
converter_config.CustomDiagramConfig(
capella_attr="context_diagram",
polarion_id="context_diagram",
title="Context Diagram",
render_params=render_params,
filters=filters,
)
],
)

def _add_cable_tree_diagram(
def _add_tree_diagram(
self,
converter_data: data_session.ConverterData,
render_params: dict[str, t.Any] | None = None,
filters: list[str] | None = None,
) -> data_model.CapellaWorkItem:
return self._add_diagram(
warnings.warn(
"Using 'add_tree_diagram' is deprecated. "
"Use 'add_custom_diagrams' instead.",
DeprecationWarning,
stacklevel=2,
)
return self._add_custom_diagrams(
converter_data,
"cable_tree",
"Cable Tree Diagram",
render_params,
filters,
[
converter_config.CustomDiagramConfig(
capella_attr="tree_view",
polarion_id="tree_view",
title="Tree View",
render_params=render_params,
filters=filters,
)
],
)

def _add_jinja_fields(
Expand All @@ -583,14 +570,15 @@ def _add_jinja_fields(
"""Add a new custom field and fill it with rendered jinja content."""
assert converter_data.work_item, "No work item set yet"
for field, jinja_properties in fields.items():
converter_data.work_item.additional_attributes[field] = {
"type": "text/html",
"value": self._render_jinja_template(
jinja_properties.get("template_folder", ""),
jinja_properties["template_path"],
converter_data,
),
}
converter_data.work_item.additional_attributes[field] = (
polarion_api.HtmlContent(
self._render_jinja_template(
jinja_properties.get("template_folder", ""),
jinja_properties["template_path"],
converter_data,
),
)
)

return converter_data.work_item

Expand Down
15 changes: 9 additions & 6 deletions docs/source/configuration/sync.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,20 @@ serializers in the configs. These will be called in the order provided in the
list. Some serializers also support additional configuration. This can be done
by providing a dictionary of serializers with the serializer as key and the
configuration of the serializer as value. For example ``Class`` using the
``add_tree_diagram`` serializer:
``add_custom_diagrams`` serializer to render the tree view diagram from the
``tree_view`` Capella attribute into a custom field with the ID ``tree_view``
and title ``Tree View ``:
.. literalinclude:: ../../../tests/data/model_elements/config.yaml
:language: yaml
:lines: 9-13
or ``SystemFunction`` with the ``add_context_diagram`` serializer using ``filters``:
or ``SystemFunction`` with the ``add_custom_diagrams`` serializer using
``filters``:

.. literalinclude:: ../../../tests/data/model_elements/config.yaml
:language: yaml
:lines: 64-67
:lines: 69-72

If a serializer supports additional parameters this will be documented in the
supported capella serializers table in :ref:`Model synchronization
Expand All @@ -73,7 +76,7 @@ to the desired Polarion type.

.. literalinclude:: ../../../tests/data/model_elements/config.yaml
:language: yaml
:lines: 73-91
:lines: 84-99

For the ``PhysicalComponent`` you can see this in extreme action, where based
on the different permutation of the attributes actor and nature different
Expand All @@ -90,14 +93,14 @@ Links can be configured by just providing a list of strings:

.. literalinclude:: ../../../tests/data/model_elements/config.yaml
:language: yaml
:lines: 33-37
:lines: 36-37

However there is a more verbose way that gives you the option to configure the
link further:

.. literalinclude:: ../../../tests/data/model_elements/config.yaml
:language: yaml
:lines: 52-63
:lines: 59-68

The links of ``SystemFunction`` are configured such that a ``polarion_role``,
a separate ``capella_attr``, an ``include``, ``link_field`` and
Expand Down
Loading

0 comments on commit abf1288

Please sign in to comment.