From 62261de1d7c0a89a977ded7255b0d61b4457104f Mon Sep 17 00:00:00 2001 From: Michael Harbarth Date: Tue, 16 Jan 2024 16:46:20 +0100 Subject: [PATCH 1/8] feat: Introduce a new force-update-work-item flag to patch workitems even if their checksum does not differ --- capella2polarion/__main__.py | 7 +++- capella2polarion/cli.py | 2 ++ .../connectors/polarion_worker.py | 4 ++- tests/test_elements.py | 32 +++++++++++++++++++ 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/capella2polarion/__main__.py b/capella2polarion/__main__.py index 3cc2f83..cdddec4 100644 --- a/capella2polarion/__main__.py +++ b/capella2polarion/__main__.py @@ -20,6 +20,7 @@ @click.group() @click.option("--debug", is_flag=True, default=False) +@click.option("--force-update-work-items", is_flag=True, default=False) @click.option( "--polarion-project-id", type=str, @@ -56,6 +57,7 @@ def cli( ctx: click.core.Context, debug: bool, + force_update_work_items: bool, polarion_project_id: str, polarion_url: str, polarion_pat: str, @@ -74,6 +76,7 @@ def cli( capella_diagram_cache_folder_path, capella_model, synchronize_config, + force_update_work_items, ) capella2polarion_cli.setup_logger() ctx.obj = capella2polarion_cli @@ -117,7 +120,9 @@ def synchronize(ctx: click.core.Context) -> None: ) polarion_worker = pw.CapellaPolarionWorker( - capella_to_polarion_cli.polarion_params, capella_to_polarion_cli.config + capella_to_polarion_cli.polarion_params, + capella_to_polarion_cli.config, + capella_to_polarion_cli.force_update_work_items, ) polarion_worker.load_polarion_work_item_map() diff --git a/capella2polarion/cli.py b/capella2polarion/cli.py index b667776..e240bfe 100644 --- a/capella2polarion/cli.py +++ b/capella2polarion/cli.py @@ -30,6 +30,7 @@ def __init__( capella_diagram_cache_folder_path: pathlib.Path | None, capella_model: capellambse.MelodyModel, synchronize_config_io: typing.TextIO, + force_update_work_items: bool = False, ) -> None: self.debug = debug self.polarion_params = pw.PolarionWorkerParams( @@ -55,6 +56,7 @@ def __init__( self.synchronize_config_content: dict[str, typing.Any] = {} self.synchronize_config_roles: dict[str, list[str]] | None = None self.config = converter_config.ConverterConfig() + self.force_update_work_items = force_update_work_items def _none_save_value_string(self, value: str | None) -> str | None: return "None" if value is None else value diff --git a/capella2polarion/connectors/polarion_worker.py b/capella2polarion/connectors/polarion_worker.py index bbf244f..2d44afc 100644 --- a/capella2polarion/connectors/polarion_worker.py +++ b/capella2polarion/connectors/polarion_worker.py @@ -35,10 +35,12 @@ def __init__( self, params: PolarionWorkerParams, config: converter_config.ConverterConfig, + force_update_work_items: bool = False, ) -> None: self.polarion_params = params self.polarion_data_repo = polarion_repo.PolarionDataRepository() self.config = config + self.force_update_work_items = force_update_work_items if (self.polarion_params.project_id is None) or ( len(self.polarion_params.project_id) == 0 @@ -151,7 +153,7 @@ def patch_work_item( """Patch a given WorkItem.""" new = converter_session[uuid].work_item _, old = self.polarion_data_repo[uuid] - if new == old: + if not self.force_update_work_items and new == old: return assert old is not None diff --git a/tests/test_elements.py b/tests/test_elements.py index 69e9cb8..f4244cb 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -719,6 +719,38 @@ def test_update_work_items_filters_work_items_with_same_checksum( assert base_object.pw.client is not None assert base_object.pw.client.update_work_item.call_count == 0 + @staticmethod + def test_update_work_items_same_checksum_force( + base_object: BaseObjectContainer, + ): + base_object.pw.force_update_work_items = True + base_object.pw.polarion_data_repo.update_work_items( + [ + data_models.CapellaWorkItem( + id="Obj-1", + uuid_capella="uuid1", + status="open", + checksum=TEST_WI_CHECKSUM, + type="fakeModelObject", + ) + ] + ) + base_object.mc.converter_session[ + "uuid1" + ].work_item = data_models.CapellaWorkItem( + id="Obj-1", + uuid_capella="uuid1", + status="open", + type="fakeModelObject", + ) + + del base_object.mc.converter_session["uuid2"] + + base_object.pw.patch_work_items(base_object.mc.converter_session) + + assert base_object.pw.client is not None + assert base_object.pw.client.update_work_item.call_count == 1 + @staticmethod def test_update_links_with_no_elements(base_object: BaseObjectContainer): base_object.pw.polarion_data_repo = ( From ecf5f28468a1c0d5693bcf7571860299de2bec05 Mon Sep 17 00:00:00 2001 From: Michael Harbarth Date: Wed, 17 Jan 2024 09:52:07 +0100 Subject: [PATCH 2/8] fix: Get the work item from polarion, if deviations are detected and update not present fields with empty values --- .../connectors/polarion_worker.py | 34 ++++++++++++++++--- tests/test_elements.py | 26 +++++++++++++- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/capella2polarion/connectors/polarion_worker.py b/capella2polarion/connectors/polarion_worker.py index 2d44afc..2584117 100644 --- a/capella2polarion/connectors/polarion_worker.py +++ b/capella2polarion/connectors/polarion_worker.py @@ -5,6 +5,7 @@ import collections.abc as cabc import logging +import typing as t from urllib import parse import polarion_rest_api_client as polarion_api @@ -161,12 +162,37 @@ def patch_work_item( log_args = (old.id, new.type, new.title) logger.info("Update work item %r for model %s %r...", *log_args) - if "uuid_capella" in new.additional_attributes: - del new.additional_attributes["uuid_capella"] - old.linked_work_items = self.client.get_all_work_item_links(old.id) - new.type = None + del new.additional_attributes["uuid_capella"] + + old = self.client.get_work_item(old.id) + + # If there were to many linked work items, get them manually + if old.linked_work_items_truncated: + old.linked_work_items = self.client.get_all_work_item_links(old.id) + + del old.additional_attributes["uuid_capella"] + + # We should only send the type to be updated, if it really changed + if new.type == old.type: + new.type = None new.status = "open" + + # If additional fields were present in the past, but aren't anymore, + # we have to set them to an empty value manually + for attribute, value in old.additional_attributes.items(): + if attribute not in new.additional_attributes: + new_value: t.Any = None + if isinstance(value, str): + new_value = "" + elif isinstance(value, int): + new_value = 0 + elif isinstance(value, bool): + new_value = False + new.additional_attributes[attribute] = new_value + elif new.additional_attributes[attribute] == value: + del new.additional_attributes[attribute] + assert new.id is not None try: self.client.update_work_item(new) diff --git a/tests/test_elements.py b/tests/test_elements.py index f4244cb..725ab49 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -672,12 +672,21 @@ def test_update_work_items( del base_object.mc.converter_session["uuid2"] + get_work_item_mock = mock.MagicMock() + get_work_item_mock.return_value = polarion_work_item_list[0] + monkeypatch.setattr( + base_object.pw.client, + "get_work_item", + get_work_item_mock, + ) + base_object.pw.patch_work_items(base_object.mc.converter_session) assert base_object.pw.client is not None - assert base_object.pw.client.get_all_work_item_links.call_count == 1 + assert base_object.pw.client.get_all_work_item_links.call_count == 0 assert base_object.pw.client.delete_work_item_links.call_count == 0 assert base_object.pw.client.create_work_item_links.call_count == 0 assert base_object.pw.client.update_work_item.call_count == 1 + assert base_object.pw.client.get_work_item.call_count == 1 work_item = base_object.pw.client.update_work_item.call_args[0][0] assert isinstance(work_item, data_models.CapellaWorkItem) assert work_item.id == "Obj-1" @@ -806,6 +815,21 @@ def test_update_links(base_object: BaseObjectContainer): base_object.mc.generate_work_item_links( base_object.pw.polarion_data_repo ) + + work_item_1 = data_models.CapellaWorkItem( + **base_object.pw.polarion_data_repo["uuid1"][1].to_dict() + ) + work_item_2 = data_models.CapellaWorkItem( + **base_object.pw.polarion_data_repo["uuid2"][1].to_dict() + ) + work_item_1.linked_work_items_truncated = True + work_item_2.linked_work_items_truncated = True + + base_object.pw.client.get_work_item.side_effect = ( + work_item_1, + work_item_2, + ) + base_object.pw.patch_work_items(base_object.mc.converter_session) assert base_object.pw.client is not None links = base_object.pw.client.get_all_work_item_links.call_args_list From 595d9940c6305a95bed1f4c2997f2eed8832a153 Mon Sep 17 00:00:00 2001 From: micha91 Date: Wed, 17 Jan 2024 14:08:16 +0100 Subject: [PATCH 3/8] refactor: Force_update_work_items --> force_update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernst Würger <50786483+ewuerger@users.noreply.github.com> --- capella2polarion/__main__.py | 8 ++++---- capella2polarion/cli.py | 4 ++-- capella2polarion/connectors/polarion_worker.py | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/capella2polarion/__main__.py b/capella2polarion/__main__.py index cdddec4..8c653c5 100644 --- a/capella2polarion/__main__.py +++ b/capella2polarion/__main__.py @@ -20,7 +20,7 @@ @click.group() @click.option("--debug", is_flag=True, default=False) -@click.option("--force-update-work-items", is_flag=True, default=False) +@click.option("--force-update", is_flag=True, default=False) @click.option( "--polarion-project-id", type=str, @@ -57,7 +57,7 @@ def cli( ctx: click.core.Context, debug: bool, - force_update_work_items: bool, + force_update: bool, polarion_project_id: str, polarion_url: str, polarion_pat: str, @@ -76,7 +76,7 @@ def cli( capella_diagram_cache_folder_path, capella_model, synchronize_config, - force_update_work_items, + force_update, ) capella2polarion_cli.setup_logger() ctx.obj = capella2polarion_cli @@ -122,7 +122,7 @@ def synchronize(ctx: click.core.Context) -> None: polarion_worker = pw.CapellaPolarionWorker( capella_to_polarion_cli.polarion_params, capella_to_polarion_cli.config, - capella_to_polarion_cli.force_update_work_items, + capella_to_polarion_cli.force_update, ) polarion_worker.load_polarion_work_item_map() diff --git a/capella2polarion/cli.py b/capella2polarion/cli.py index e240bfe..c38c2bd 100644 --- a/capella2polarion/cli.py +++ b/capella2polarion/cli.py @@ -30,7 +30,7 @@ def __init__( capella_diagram_cache_folder_path: pathlib.Path | None, capella_model: capellambse.MelodyModel, synchronize_config_io: typing.TextIO, - force_update_work_items: bool = False, + force_update: bool = False, ) -> None: self.debug = debug self.polarion_params = pw.PolarionWorkerParams( @@ -56,7 +56,7 @@ def __init__( self.synchronize_config_content: dict[str, typing.Any] = {} self.synchronize_config_roles: dict[str, list[str]] | None = None self.config = converter_config.ConverterConfig() - self.force_update_work_items = force_update_work_items + self.force_update = force_update def _none_save_value_string(self, value: str | None) -> str | None: return "None" if value is None else value diff --git a/capella2polarion/connectors/polarion_worker.py b/capella2polarion/connectors/polarion_worker.py index 2584117..2f3a5d7 100644 --- a/capella2polarion/connectors/polarion_worker.py +++ b/capella2polarion/connectors/polarion_worker.py @@ -36,12 +36,12 @@ def __init__( self, params: PolarionWorkerParams, config: converter_config.ConverterConfig, - force_update_work_items: bool = False, + force_update: bool = False, ) -> None: self.polarion_params = params self.polarion_data_repo = polarion_repo.PolarionDataRepository() self.config = config - self.force_update_work_items = force_update_work_items + self.force_update = force_update if (self.polarion_params.project_id is None) or ( len(self.polarion_params.project_id) == 0 @@ -154,7 +154,7 @@ def patch_work_item( """Patch a given WorkItem.""" new = converter_session[uuid].work_item _, old = self.polarion_data_repo[uuid] - if not self.force_update_work_items and new == old: + if not self.force_update and new == old: return assert old is not None From 51d4b65814f80e3b7a9770ecb7075fe69fda5224 Mon Sep 17 00:00:00 2001 From: micha91 Date: Wed, 17 Jan 2024 14:09:32 +0100 Subject: [PATCH 4/8] refactor: Remove unneeded comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernst Würger <50786483+ewuerger@users.noreply.github.com> --- capella2polarion/connectors/polarion_worker.py | 1 - 1 file changed, 1 deletion(-) diff --git a/capella2polarion/connectors/polarion_worker.py b/capella2polarion/connectors/polarion_worker.py index 2f3a5d7..f7cb2f8 100644 --- a/capella2polarion/connectors/polarion_worker.py +++ b/capella2polarion/connectors/polarion_worker.py @@ -167,7 +167,6 @@ def patch_work_item( old = self.client.get_work_item(old.id) - # If there were to many linked work items, get them manually if old.linked_work_items_truncated: old.linked_work_items = self.client.get_all_work_item_links(old.id) From 89f624ac59a666b804259d669ebb6f3c3fb1d266 Mon Sep 17 00:00:00 2001 From: Michael Harbarth Date: Wed, 17 Jan 2024 14:15:27 +0100 Subject: [PATCH 5/8] fix: Test for force mode --- tests/test_elements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_elements.py b/tests/test_elements.py index 725ab49..e2a8f84 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -732,7 +732,7 @@ def test_update_work_items_filters_work_items_with_same_checksum( def test_update_work_items_same_checksum_force( base_object: BaseObjectContainer, ): - base_object.pw.force_update_work_items = True + base_object.pw.force_update = True base_object.pw.polarion_data_repo.update_work_items( [ data_models.CapellaWorkItem( From 7f3fa6f974349cb40597cc7bee8ebc4213ffd9eb Mon Sep 17 00:00:00 2001 From: Michael Harbarth Date: Wed, 17 Jan 2024 14:17:56 +0100 Subject: [PATCH 6/8] refactor: Use a defaults dictionary instead of multiple if elses --- .../connectors/polarion_worker.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/capella2polarion/connectors/polarion_worker.py b/capella2polarion/connectors/polarion_worker.py index f7cb2f8..3561852 100644 --- a/capella2polarion/connectors/polarion_worker.py +++ b/capella2polarion/connectors/polarion_worker.py @@ -17,6 +17,13 @@ logger = logging.getLogger(__name__) +DEFAULT_ATTRIBUTE_VALUES: dict[type, t.Any] = { + str: "", + int: 0, + bool: False, +} + + class PolarionWorkerParams: """Container for Polarion Params.""" @@ -179,16 +186,12 @@ def patch_work_item( # If additional fields were present in the past, but aren't anymore, # we have to set them to an empty value manually + defaults = DEFAULT_ATTRIBUTE_VALUES for attribute, value in old.additional_attributes.items(): if attribute not in new.additional_attributes: - new_value: t.Any = None - if isinstance(value, str): - new_value = "" - elif isinstance(value, int): - new_value = 0 - elif isinstance(value, bool): - new_value = False - new.additional_attributes[attribute] = new_value + new.additional_attributes[attribute] = defaults.get( + type(value) + ) elif new.additional_attributes[attribute] == value: del new.additional_attributes[attribute] From 6f822c5a1242c07ded2cef901fa733284e632369 Mon Sep 17 00:00:00 2001 From: Michael Harbarth Date: Wed, 17 Jan 2024 16:32:17 +0100 Subject: [PATCH 7/8] docs: Rewrite code comment --- capella2polarion/connectors/polarion_worker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/capella2polarion/connectors/polarion_worker.py b/capella2polarion/connectors/polarion_worker.py index 3561852..37aceed 100644 --- a/capella2polarion/connectors/polarion_worker.py +++ b/capella2polarion/connectors/polarion_worker.py @@ -179,7 +179,7 @@ def patch_work_item( del old.additional_attributes["uuid_capella"] - # We should only send the type to be updated, if it really changed + # Type will only be updated, if it is set and should be used carefully if new.type == old.type: new.type = None new.status = "open" From 83bf32daeb748b962318ba0cf10fd5524ae0b863 Mon Sep 17 00:00:00 2001 From: Michael Harbarth Date: Wed, 17 Jan 2024 16:56:50 +0100 Subject: [PATCH 8/8] feat: Add force-update flag to the gitlab ci-template and read it from CAPELLA2POLARION_FORCE_UPDATE --- ci-templates/gitlab/synchronise_elements.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/ci-templates/gitlab/synchronise_elements.yml b/ci-templates/gitlab/synchronise_elements.yml index 3df3f9f..94d4a0f 100644 --- a/ci-templates/gitlab/synchronise_elements.yml +++ b/ci-templates/gitlab/synchronise_elements.yml @@ -15,6 +15,7 @@ capella2polarion_synchronise_elements: python \ -m capella2polarion \ $([[ $CAPELLA2POLARION_DEBUG -eq 1 ]] && echo '--debug') \ + $([[ $CAPELLA2POLARION_FORCE_UPDATE -eq 1 ]] && echo '--force-update') \ --polarion-project-id=${CAPELLA2POLARION_PROJECT_ID:?} \ --capella-model="${CAPELLA2POLARION_MODEL_JSON:?}" \ --capella-diagram-cache-folder-path=./diagram_cache \