Skip to content

Commit

Permalink
fix!: Generation of grouped linkfields
Browse files Browse the repository at this point in the history
Before the linkfield wasn't taken into account
when creating the grouped linkfields.
Additionally the feature of generating grouped
linkfields and the reverse is now configurable
by using the `--grouped-links-custom-fields`
flag or setting the `CAPELLA2POLARION_GROUPED_LINKS_CUSTOM_FIELDS` environment variable.
  • Loading branch information
ewuerger committed Sep 26, 2024
1 parent a0e79a1 commit 9954153
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 46 deletions.
8 changes: 8 additions & 0 deletions capella2polarion/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,20 @@ def print_cli_state(capella2polarion_cli: Capella2PolarionCli) -> None:
@click.option("--force-update", is_flag=True, default=False)
@click.option("--type-prefix", type=str, default="")
@click.option("--role-prefix", type=str, default="")
@click.option(
"--grouped-links-custom-fields",
envvar="CAPELLA2POLARION_GROUPED_LINKS_CUSTOM_FIELDS",
is_flag=True,
default=False,
)
@click.pass_context
def synchronize(
ctx: click.core.Context,
synchronize_config: typing.TextIO,
force_update: bool,
type_prefix: str,
role_prefix: str,
grouped_links_custom_fields: bool,
) -> None:
"""Synchronise model elements."""
capella_to_polarion_cli: Capella2PolarionCli = ctx.obj
Expand Down Expand Up @@ -124,6 +131,7 @@ def synchronize(
polarion_worker.polarion_data_repo,
generate_links=True,
generate_attachments=True,
generate_grouped_links_custom_fields=grouped_links_custom_fields,
)

polarion_worker.compare_and_update_work_items(converter.converter_session)
Expand Down
49 changes: 26 additions & 23 deletions capella2polarion/converters/link_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ def __init__(
converter_config.DIAGRAM_ELEMENTS_SERIALIZER: self._handle_diagram_reference_links, # pylint: disable=line-too-long
}

self._link_field_groups: dict[str, list[polarion_api.WorkItemLink]] = (
defaultdict(list)
)

def create_links_for_work_item(
self, uuid: str
) -> list[polarion_api.WorkItemLink]:
Expand All @@ -58,6 +62,7 @@ def create_links_for_work_item(
work_item = converter_data.work_item
assert work_item is not None
assert work_item.id is not None
self._link_field_groups.clear()
new_links: list[polarion_api.WorkItemLink] = []
link_errors: list[str] = []
for link_config in converter_data.type_config.links:
Expand All @@ -66,9 +71,7 @@ def create_links_for_work_item(
try:
assert work_item.id is not None
if serializer:
new_links.extend(
serializer(obj, work_item.id, role_id, {})
)
links = serializer(obj, work_item.id, role_id, {})
else:
refs = _resolve_attribute(obj, link_config.capella_attr)
new: cabc.Iterable[str]
Expand All @@ -88,9 +91,10 @@ def create_links_for_work_item(
new = set(
self._get_work_item_ids(work_item.id, new, role_id)
)
new_links.extend(
self._create(work_item.id, role_id, new, {})
)
links = self._create(work_item.id, role_id, new, {})

new_links.extend(links)
self._link_field_groups[link_config.link_field].extend(links)
except Exception as error:
error_message = make_link_logging_message(
f"{type(error).__name__} {str(error)}",
Expand Down Expand Up @@ -223,23 +227,22 @@ def create_grouped_link_fields(
assert work_item is not None
wi = f"[{work_item.id}]({work_item.type} {work_item.title})"
logger.debug("Building grouped links for work item %r...", wi)
for role, grouped_links in _group_by(
"role", work_item.linked_work_items
).items():
if (config := find_link_config(data, role)) is not None:
if back_links is not None and config.reverse_field:
for link in grouped_links:
back_links.setdefault(
link.secondary_work_item_id, {}
).setdefault(config.reverse_field, []).append(link)

if config.link_field:
self._create_link_fields(
work_item,
config.link_field,
grouped_links,
config=config,
)
for link_config in data.type_config.links:
grouped_links = self._link_field_groups[link_config.link_field]

if back_links is not None and link_config.reverse_field:
for link in grouped_links:
back_links.setdefault(
link.secondary_work_item_id, {}
).setdefault(link_config.reverse_field, []).append(link)

if grouped_links:
self._create_link_fields(
work_item,
link_config.link_field,
grouped_links,
config=link_config,
)

def _create_link_fields(
self,
Expand Down
16 changes: 12 additions & 4 deletions capella2polarion/converters/model_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def generate_work_items(
polarion_data_repo: polarion_repo.PolarionDataRepository,
generate_links: bool = False,
generate_attachments: bool = False,
generate_grouped_links_custom_fields: bool = False,
) -> dict[str, data_models.CapellaWorkItem]:
"""Return a work items mapping from model elements for Polarion.
Expand All @@ -100,6 +101,9 @@ def generate_work_items(
generate_attachments
A boolean flag to control attachments generation. For SVG
attachments, PNGs are generated and attached automatically.
generate_grouped_links_custom_fields
A boolean flag to control grouped links custom fields
generation.
"""
serializer = element_converter.CapellaWorkItemSerializer(
self.model,
Expand All @@ -113,13 +117,16 @@ def generate_work_items(
assert work_item.type is not None

if generate_links:
self.generate_work_item_links(polarion_data_repo)
self.generate_work_item_links(
polarion_data_repo, generate_grouped_links_custom_fields
)

return {wi.uuid_capella: wi for wi in work_items}

def generate_work_item_links(
self,
polarion_data_repo: polarion_repo.PolarionDataRepository,
generate_grouped_links_custom_fields: bool,
):
"""Generate links for all work items and add custom fields for them."""
back_links: dict[str, dict[str, list[polarion_api.WorkItemLink]]] = {}
Expand All @@ -140,9 +147,10 @@ def generate_work_item_links(
links = link_serializer.create_links_for_work_item(uuid)
converter_data.work_item.linked_work_items = links

link_serializer.create_grouped_link_fields(
converter_data, back_links
)
if generate_grouped_links_custom_fields:
link_serializer.create_grouped_link_fields(
converter_data, back_links
)

for uuid, converter_data in self.converter_session.items():
if converter_data.work_item is None:
Expand Down
19 changes: 8 additions & 11 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,10 @@ class UnsupportedFakeModelObject(FakeModelObject):
"""A ``FakeModelObject`` which shouldn't be migrated."""


class BaseObjectContainer:
def __init__(
self,
c2p_cli: cli.Capella2PolarionCli,
pw: polarion_worker.CapellaPolarionWorker,
mc: model_converter.ModelConverter,
) -> None:
self.c2pcli: cli.Capella2PolarionCli = c2p_cli
self.pw: polarion_worker.CapellaPolarionWorker = pw
self.mc = mc
class BaseObjectContainer(t.NamedTuple):
c2pcli: cli.Capella2PolarionCli
pw: polarion_worker.CapellaPolarionWorker
mc: model_converter.ModelConverter


# pylint: disable=redefined-outer-name
Expand Down Expand Up @@ -144,7 +138,10 @@ def base_object(
"fakeModelObject",
links=[
converter_config.LinkConfig(
capella_attr="attribute", polarion_role="attribute"
capella_attr="attribute",
polarion_role="attribute",
link_field="attribute",
reverse_field="attribute_reverse",
)
],
)
Expand Down
2 changes: 2 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def test_migrate_model_elements(monkeypatch: pytest.MonkeyPatch):
"synchronize",
"--synchronize-config",
str(TEST_MODEL_ELEMENTS_CONFIG),
"--grouped-links-custom-fields",
]

result = testing.CliRunner().invoke(main.cli, command, terminal_width=60)
Expand All @@ -81,6 +82,7 @@ def test_migrate_model_elements(monkeypatch: pytest.MonkeyPatch):
assert mock_generate_work_items.call_args_list[1][1] == {
"generate_links": True,
"generate_attachments": True,
"generate_grouped_links_custom_fields": True,
}
assert mock_delete_work_items.call_count == 1
assert mock_patch_work_items.call_count == 1
Expand Down
78 changes: 70 additions & 8 deletions tests/test_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import logging
import typing as t
from collections import defaultdict
from unittest import mock

import capellambse
Expand Down Expand Up @@ -1001,7 +1002,8 @@ def test_update_links(base_object: BaseObjectContainer):
"Obj-2", "Obj-1", "attribute", None, "project_id"
)
base_object.mc.generate_work_item_links(
base_object.pw.polarion_data_repo
base_object.pw.polarion_data_repo,
generate_grouped_links_custom_fields=True,
)

work_item_1 = (
Expand Down Expand Up @@ -1118,7 +1120,8 @@ def mock_back_link(converter_data, back_links):
]
base_object.mc.model = mock_model
base_object.mc.generate_work_item_links(
base_object.pw.polarion_data_repo
base_object.pw.polarion_data_repo,
generate_grouped_links_custom_fields=True,
)
base_object.pw.compare_and_update_work_items(
base_object.mc.converter_session
Expand Down Expand Up @@ -1185,10 +1188,14 @@ def test_maintain_grouped_links_attributes(
converter_data = data_session.ConverterData(
"test", config, [], work_item
)
link_serializer._link_field_groups["attribute"] = (
work_item.linked_work_items
)
link_serializer.create_grouped_link_fields(converter_data)
del dummy_work_items["uuid0"].additional_attributes["uuid_capella"]
del dummy_work_items["uuid1"].additional_attributes["uuid_capella"]
del dummy_work_items["uuid2"].additional_attributes["uuid_capella"]

assert (
dummy_work_items["uuid0"].additional_attributes.pop("attribute")[
"value"
Expand Down Expand Up @@ -1244,13 +1251,63 @@ def test_maintain_grouped_links_attributes_with_role_prefix(
converter_data = data_session.ConverterData(
"test", config, [], work_item
)
if work_item.uuid_capella == "uuid0":
link_serializer._link_field_groups["attribute"] = (
work_item.linked_work_items
)

link_serializer.create_grouped_link_fields(converter_data)
link_serializer._link_field_groups = defaultdict(list)

assert "attribute" in dummy_work_items["uuid0"].additional_attributes
assert ( # Link Role on links were not prefixed
"attribute" not in dummy_work_items["uuid1"].additional_attributes
)

@staticmethod
def test_grouped_links_attributes_different_link_field_in_config(
base_object: BaseObjectContainer, monkeypatch: pytest.MonkeyPatch
):
converter_data_1 = base_object.mc.converter_session["uuid1"]
converter_data_2 = base_object.mc.converter_session["uuid2"]
converter_data_2.work_item = data_models.CapellaWorkItem(
id="Obj-2", uuid_capella="uuid2", status="open"
)
base_object.pw.polarion_data_repo.update_work_items(
[converter_data_2.work_item]
)
converter_data_1.type_config.links.append(
converter_config.LinkConfig(
capella_attr="attribute1",
polarion_role="attribute",
link_field="attribute1",
reverse_field="attribute1_reverse",
)
)
converter_data_1.capella_element.attribute = (
converter_data_2.capella_element
)
converter_data_1.capella_element.attribute1 = (
converter_data_2.capella_element
)
expected_html = (
"<ul><li>"
'<span class="polarion-rte-link" data-type="workItem" id="fake" '
'data-item-id="Obj-2" data-option-id="long"></span>'
"</li></ul>"
)

base_object.mc.generate_work_item_links(
base_object.pw.polarion_data_repo,
generate_grouped_links_custom_fields=True,
)

assert (link_group := getattr(converter_data_1.work_item, "attribute"))
assert (
link_group1 := getattr(converter_data_1.work_item, "attribute1")
)
assert link_group["value"] == link_group1["value"] == expected_html

@staticmethod
def test_grouped_links_attributes_with_includes(
base_object: BaseObjectContainer, model: capellambse.MelodyModel
Expand Down Expand Up @@ -1352,6 +1409,9 @@ def test_maintain_reverse_grouped_links_attributes(
data[work_item.id] = converter_data = data_session.ConverterData(
"test", config, [], work_item
)
link_serializer._link_field_groups["attribute"] = (
work_item.linked_work_items
)
link_serializer.create_grouped_link_fields(
converter_data, back_links
)
Expand Down Expand Up @@ -1395,6 +1455,9 @@ def test_maintain_reverse_grouped_links_unidirectional_config(
FakeModelObject(work_item.uuid_capella),
work_item,
)
link_serializer._link_field_groups["attribute"] = (
work_item.linked_work_items
)
link_serializer.create_grouped_link_fields(
converter_data, back_links
)
Expand Down Expand Up @@ -1427,19 +1490,18 @@ def test_maintain_reverse_grouped_links_attributes_with_role_prefix(
config.links[0].polarion_role = f"_C2P_{config.links[0].polarion_role}"
back_links: dict[str, dict[str, list[polarion_api.WorkItemLink]]] = {}
data = {}
for link in (
dummy_work_items["uuid0"].linked_work_items
+ dummy_work_items["uuid1"].linked_work_items
):
link.role = f"_C2P_{link.role}"

for work_item in dummy_work_items.values():
for link in work_item.linked_work_items:
link_serializer._link_field_groups[link.role].append(link)
link.role = f"_C2P_{link.role}"

data[work_item.id] = converter_data = data_session.ConverterData(
"test", config, [], work_item
)
link_serializer.create_grouped_link_fields(
converter_data, back_links
)

for work_item_id, links in back_links.items():
assert (wi := data[work_item_id].work_item) is not None
link_serializer.create_grouped_back_link_fields(wi, links)
Expand Down

0 comments on commit 9954153

Please sign in to comment.