diff --git a/capella2polarion/elements/serialize.py b/capella2polarion/elements/serialize.py index 6a0bfcf8..7a1ee35d 100644 --- a/capella2polarion/elements/serialize.py +++ b/capella2polarion/elements/serialize.py @@ -4,6 +4,7 @@ from __future__ import annotations import base64 +import collections import collections.abc as cabc import logging import mimetypes @@ -113,6 +114,7 @@ def _generic_work_item( raw_description = getattr(obj, "description", markupsafe.Markup("")) uuids, value = _sanitize_description(obj, raw_description, ctx) ctx.setdefault("DESCR_REFERENCES", {})[obj.uuid] = uuids + requirement_types = _get_requirement_types_text(obj) return CapellaWorkItem( type=helpers.resolve_element_type(xtype), title=obj.name, @@ -120,9 +122,48 @@ def _generic_work_item( description=value, status="open", uuid_capella=obj.uuid, + **requirement_types, ) +def _get_requirement_types_text( + obj: common.GenericElement, +) -> dict[str, dict[str, str]]: + type_texts = collections.defaultdict(list) + for req in obj.requirements: + if req is None: + logger.error( + "RequirementsRelation with broken target found %r", obj.name + ) + continue + + if not (req.type and req.text): + logger.warning( + "Requirement without text or type found %r", req.name + ) + continue + + type_texts[req.type.long_name].append(req.text) + return _format_texts(type_texts) + + +def _format_texts( + type_texts: dict[str, list[str]] +) -> dict[str, dict[str, str]]: + def _format(texts: list[str]) -> dict[str, str]: + if len(texts) > 1: + items = "".join(f"
  • {text}
  • " for text in texts) + text = f"" + else: + text = texts[0] + return {"type": "text/html", "value": text} + + requirement_types = {} + for typ, texts in type_texts.items(): + requirement_types[typ.lower()] = _format(texts) + return requirement_types + + def _sanitize_description( obj: common.GenericElement, descr: markupsafe.Markup, ctx: dict[str, t.Any] ) -> tuple[list[str], markupsafe.Markup]: diff --git a/tests/test_elements.py b/tests/test_elements.py index de07e915..7d0f5800 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -62,6 +62,12 @@ TEST_WI_CHECKSUM = ( "73508ec0c3048c5b33316dfa56ef5e5f4179ff69efaa209e47ab65b111415e82" ) +TEST_REQ_TEXT = ( + "

    Test requirement 1 really l o n g text that is way too long to " + "display here as that

    \n\n

    < > " '

    \n\n\n\n
      \n\t" + "
    1. Ordered list
    2. \n\t
    3. Ok
    4. \n
    \n" +) class TestDiagramElements: @@ -478,7 +484,7 @@ def test__decode_diagram(): @pytest.mark.parametrize( "uuid,expected", [ - ( + pytest.param( TEST_ELEMENT_UUID, { "type": "logicalComponent", @@ -486,9 +492,14 @@ def test__decode_diagram(): "uuid_capella": TEST_ELEMENT_UUID, "description_type": "text/html", "description": markupsafe.Markup(TEST_DESCR), + "reqtype": { + "type": "text/html", + "value": markupsafe.Markup(TEST_REQ_TEXT), + }, }, + id="logicalComponent", ), - ( + pytest.param( TEST_OCAP_UUID, { "type": "operationalCapability", @@ -501,8 +512,9 @@ def test__decode_diagram(): "postCondition": {"type": "text/html", "value": ""}, }, }, + id="operationalCapability", ), - ( + pytest.param( TEST_WE_UUID, { "type": "entity", @@ -511,8 +523,9 @@ def test__decode_diagram(): "description_type": "text/html", "description": markupsafe.Markup(TEST_WE_DESCR), }, + id="entity", ), - ( + pytest.param( TEST_ACTOR_UUID, { "type": "logicalActor", @@ -524,8 +537,9 @@ def test__decode_diagram(): "and greatest mage of all time.

    \n" ), }, + id="logicalActor", ), - ( + pytest.param( TEST_PHYS_COMP, { "type": "physicalComponent", @@ -534,8 +548,9 @@ def test__decode_diagram(): "description_type": "text/html", "description": markupsafe.Markup(""), }, + id="physicalComponent", ), - ( + pytest.param( TEST_PHYS_NODE, { "type": "physicalComponentNode", @@ -544,8 +559,9 @@ def test__decode_diagram(): "description_type": "text/html", "description": markupsafe.Markup(""), }, + id="physicalComponentNode", ), - ( + pytest.param( TEST_SCENARIO, { "type": "scenario", @@ -558,8 +574,9 @@ def test__decode_diagram(): "postCondition": {"type": "text/html", "value": ""}, }, }, + id="scenario", ), - ( + pytest.param( TEST_CAP_REAL, { "type": "capabilityRealization", @@ -572,8 +589,9 @@ def test__decode_diagram(): "postCondition": {"type": "text/html", "value": ""}, }, }, + id="capabilityRealization", ), - ( + pytest.param( TEST_CONSTRAINT, { "type": "constraint", @@ -584,6 +602,7 @@ def test__decode_diagram(): "This is a test context.Make Food" ), }, + id="constraint", ), ], )