From 8a2cdcc6c59fc54ee79882bab5844696b88673d9 Mon Sep 17 00:00:00 2001 From: Michael Harbarth Date: Thu, 5 Sep 2024 12:03:07 +0200 Subject: [PATCH] fix: cross project work item references --- capella2polarion/__main__.py | 1 + .../converters/document_renderer.py | 40 +++++++-- .../converters/polarion_html_helper.py | 10 +++ .../document_templates/test-icd.html.j2 | 1 + tests/conftest.py | 14 +-- tests/data/documents/combined_config.yaml | 2 +- .../work_items_cross_project.html.j2 | 8 ++ tests/test_documents.py | 89 +++++++++++++++++-- 8 files changed, 142 insertions(+), 23 deletions(-) create mode 100644 tests/data/documents/templates/work_items_cross_project.html.j2 diff --git a/capella2polarion/__main__.py b/capella2polarion/__main__.py index a9e93ea6..9dbf786b 100644 --- a/capella2polarion/__main__.py +++ b/capella2polarion/__main__.py @@ -163,6 +163,7 @@ def render_documents( renderer = document_renderer.DocumentRenderer( polarion_worker.polarion_data_repo, capella_to_polarion_cli.capella_model, + capella_to_polarion_cli.polarion_params.project_id, overwrite_numbering, overwrite_layouts, ) diff --git a/capella2polarion/converters/document_renderer.py b/capella2polarion/converters/document_renderer.py index 58e7af59..15a9dc68 100644 --- a/capella2polarion/converters/document_renderer.py +++ b/capella2polarion/converters/document_renderer.py @@ -40,6 +40,7 @@ class RenderingSession: text_work_items: dict[str, polarion_api.WorkItem] = dataclasses.field( default_factory=dict ) + document_project_id: str | None = None @dataclasses.dataclass @@ -61,6 +62,7 @@ def __init__( self, polarion_repository: polarion_repo.PolarionDataRepository, model: capellambse.MelodyModel, + model_work_item_project_id: str, overwrite_heading_numbering: bool = False, overwrite_layouts: bool = False, ): @@ -71,6 +73,7 @@ def __init__( self.overwrite_layouts = overwrite_layouts self.projects: dict[str | None, ProjectData] = {} self.existing_documents: polarion_repo.DocumentRepository = {} + self.model_work_item_project_id = model_work_item_project_id def setup_env(self, env: jinja2.Environment): """Add globals and filters to the environment.""" @@ -79,6 +82,12 @@ def setup_env(self, env: jinja2.Environment): env.globals["work_item_field"] = self.__work_item_field env.filters["link_work_item"] = self.__link_work_item + def _is_external_document(self, session: RenderingSession) -> bool: + """Check if the document is in a different project than the model.""" + if session.document_project_id is None: + return False + return session.document_project_id != self.model_work_item_project_id + def __insert_work_item( self, obj: object, session: RenderingSession, level: int | None = None ) -> str: @@ -108,10 +117,17 @@ def __insert_work_item( custom_info = f"level={level}|" session.inserted_work_items.append(wi) - - return polarion_html_helper.POLARION_WORK_ITEM_DOCUMENT.format( - pid=wi.id, lid=layout_index, custom_info=custom_info - ) + if self._is_external_document(session): + return polarion_html_helper.POLARION_WORK_ITEM_DOCUMENT_PROJECT.format( + pid=wi.id, + lid=layout_index, + custom_info=custom_info, + project=self.model_work_item_project_id, + ) + else: + return polarion_html_helper.POLARION_WORK_ITEM_DOCUMENT.format( + pid=wi.id, lid=layout_index, custom_info=custom_info + ) return polarion_html_helper.RED_TEXT.format( text=f"Missing WorkItem for UUID {obj.uuid}" @@ -124,8 +140,8 @@ def __link_work_item(self, obj: object) -> str: if wi := self.polarion_repository.get_work_item_by_capella_uuid( obj.uuid ): - return polarion_html_helper.POLARION_WORK_ITEM_URL.format( - pid=wi.id + return polarion_html_helper.POLARION_WORK_ITEM_URL_PROJECT.format( + pid=wi.id, project=self.model_work_item_project_id ) return polarion_html_helper.RED_TEXT.format( @@ -168,6 +184,7 @@ def render_document( rendering_layouts: list[polarion_api.RenderingLayout] | None = None, *, text_work_item_provider: twi.TextWorkItemProvider | None = None, + document_project_id: str | None = None, **kwargs: t.Any, ) -> data_models.DocumentData: """Render a new Polarion document.""" @@ -180,6 +197,7 @@ def render_document( *, document: polarion_api.Document, text_work_item_provider: twi.TextWorkItemProvider | None = None, + document_project_id: str | None = None, **kwargs: t.Any, ) -> data_models.DocumentData: """Update an existing Polarion document.""" @@ -195,6 +213,7 @@ def render_document( rendering_layouts: list[polarion_api.RenderingLayout] | None = None, document: polarion_api.Document | None = None, text_work_item_provider: twi.TextWorkItemProvider | None = None, + document_project_id: str | None = None, **kwargs: t.Any, ) -> data_models.DocumentData: """Render a Polarion document.""" @@ -213,7 +232,7 @@ def render_document( env = self._get_jinja_env(template_folder) template = env.get_template(template_name) - session = RenderingSession() + session = RenderingSession(document_project_id=document_project_id) if document is not None: session.rendering_layouts = document.rendering_layouts or [] if document.home_page_content and document.home_page_content.value: @@ -255,6 +274,7 @@ def update_mixed_authority_document( global_parameters: dict[str, t.Any], section_parameters: dict[str, dict[str, t.Any]], text_work_item_provider: twi.TextWorkItemProvider | None = None, + document_project_id: str | None = None, ) -> data_models.DocumentData: """Update a mixed authority document.""" text_work_item_provider = ( @@ -269,7 +289,8 @@ def update_mixed_authority_document( section_areas = self._extract_section_areas(html_elements) session = RenderingSession( - rendering_layouts=document.rendering_layouts or [] + rendering_layouts=document.rendering_layouts or [], + document_project_id=document_project_id, ) env = self._get_jinja_env(template_folder) @@ -433,6 +454,7 @@ def _render_mixed_authority_documents( instance.params, instance.section_params, text_work_item_provider, + config.project_id, ) except Exception as e: logger.error( @@ -481,6 +503,7 @@ def _render_full_authority_documents( config.template, document=old_doc, text_work_item_provider=text_work_item_provider, + document_project_id=config.project_id, **instance.params, ) except Exception as e: @@ -505,6 +528,7 @@ def _render_full_authority_documents( config.heading_numbering, rendering_layouts, text_work_item_provider=text_work_item_provider, + document_project_id=config.project_id, **instance.params, ) except Exception as e: diff --git a/capella2polarion/converters/polarion_html_helper.py b/capella2polarion/converters/polarion_html_helper.py index 2cc20ac7..ea38acef 100644 --- a/capella2polarion/converters/polarion_html_helper.py +++ b/capella2polarion/converters/polarion_html_helper.py @@ -23,10 +23,20 @@ 'id="fake" data-item-id="{pid}" data-option-id="long">' "" ) +POLARION_WORK_ITEM_URL_PROJECT = ( + '' +) POLARION_WORK_ITEM_DOCUMENT = ( '
' ) +POLARION_WORK_ITEM_DOCUMENT_PROJECT = ( + '
' +) RE_DESCR_DELETED_PATTERN = re.compile( f"<deleted element ({chelpers.RE_VALID_UUID.pattern})>" ) diff --git a/jupyter-notebooks/document_templates/test-icd.html.j2 b/jupyter-notebooks/document_templates/test-icd.html.j2 index fc8e7676..e1992f0c 100644 --- a/jupyter-notebooks/document_templates/test-icd.html.j2 +++ b/jupyter-notebooks/document_templates/test-icd.html.j2 @@ -34,6 +34,7 @@ At the moment physical interface definition is not in scope for this document. The scope of the document covers Interface Requirements and Definition

{{ heading(2, "Interface Partners", session)}} +{{ insert_work_item(interface, session) }}

The figure below provides an overview of the interface partners:

There may be a diagram with 2 boxes and a blue line

diff --git a/tests/conftest.py b/tests/conftest.py index 296cd368..050c0343 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -32,6 +32,10 @@ "diagram_cache": str(TEST_DIAGRAM_CACHE), } TEST_HOST = "https://api.example.com" +TEST_PROJECT_ID = "project_id" +DOCUMENT_TEMPLATES = TEST_DOCUMENT_ROOT / "templates" +DOCUMENT_TEXT_WORK_ITEMS = "document_work_items.html.j2" +DOCUMENT_WORK_ITEMS_CROSS_PROJECT = "work_items_cross_project.html.j2" @pytest.fixture @@ -59,7 +63,7 @@ def dummy_work_items() -> dict[str, data_models.CapellaWorkItem]: description=markupsafe.Markup(""), linked_work_items=[ polarion_api.WorkItemLink( - f"Obj-{i}", f"Obj-{j}", "attribute", True, "project_id" + f"Obj-{i}", f"Obj-{j}", "attribute", True, TEST_PROJECT_ID ) for j in range(3) if (i not in (j, 2)) @@ -120,7 +124,7 @@ def base_object( ) c2p_cli = cli.Capella2PolarionCli( debug=True, - polarion_project_id="project_id", + polarion_project_id=TEST_PROJECT_ID, polarion_url=TEST_HOST, polarion_pat="PrivateAccessToken", polarion_delete_work_items=True, @@ -180,13 +184,9 @@ def empty_polarion_worker(monkeypatch: pytest.MonkeyPatch): mock_project_client = mock.MagicMock(spec=polarion_api.ProjectClient) monkeypatch.setattr(polarion_api, "ProjectClient", mock_project_client) polarion_params = polarion_worker.PolarionWorkerParams( - project_id="project_id", + project_id=TEST_PROJECT_ID, url=TEST_HOST, pat="PrivateAccessToken", delete_work_items=True, ) yield polarion_worker.CapellaPolarionWorker(polarion_params) - - -DOCUMENT_TEMPLATES = TEST_DOCUMENT_ROOT / "templates" -DOCUMENT_TEXT_WORK_ITEMS = "document_work_items.html.j2" diff --git a/tests/data/documents/combined_config.yaml b/tests/data/documents/combined_config.yaml index 3660a6d2..cadd7e3e 100644 --- a/tests/data/documents/combined_config.yaml +++ b/tests/data/documents/combined_config.yaml @@ -91,4 +91,4 @@ full_authority: - polarion_space: _default polarion_name: id1240 params: - interface: 2681f26a-e492-4e5d-8b33-92fb00a48622 + interface: d8655737-39ab-4482-a934-ee847c7ff6bd diff --git a/tests/data/documents/templates/work_items_cross_project.html.j2 b/tests/data/documents/templates/work_items_cross_project.html.j2 new file mode 100644 index 00000000..a3848123 --- /dev/null +++ b/tests/data/documents/templates/work_items_cross_project.html.j2 @@ -0,0 +1,8 @@ +{# + Copyright DB InfraGO AG and contributors + SPDX-License-Identifier: Apache-2.0 +#} + +{% set element = model.by_uuid(element) %} +{{ insert_work_item(element, session) }} +{{ element | link_work_item }} diff --git a/tests/test_documents.py b/tests/test_documents.py index 5069b46d..93c4b1cb 100644 --- a/tests/test_documents.py +++ b/tests/test_documents.py @@ -15,8 +15,10 @@ from tests.conftest import ( DOCUMENT_TEMPLATES, DOCUMENT_TEXT_WORK_ITEMS, + DOCUMENT_WORK_ITEMS_CROSS_PROJECT, TEST_COMBINED_DOCUMENT_CONFIG, TEST_DOCUMENT_ROOT, + TEST_PROJECT_ID, ) CLASSES_TEMPLATE = "test-classes.html.j2" @@ -26,6 +28,10 @@ FULL_AUTHORITY_CONFIG = TEST_DOCUMENT_ROOT / "full_authority_config.yaml" DOCUMENTS_CONFIG_JINJA = TEST_DOCUMENT_ROOT / "config.yaml.j2" MIXED_AUTHORITY_DOCUMENT = TEST_DOCUMENT_ROOT / "mixed_authority_doc.html" +PROJECT_EXTERNAL_WORKITEM_SRC = ( + '

' +) def existing_documents() -> polarion_repo.DocumentRepository: @@ -105,7 +111,7 @@ def test_create_new_document( ] ) renderer = document_renderer.DocumentRenderer( - empty_polarion_worker.polarion_data_repo, model + empty_polarion_worker.polarion_data_repo, model, TEST_PROJECT_ID ) document_data = renderer.render_document( @@ -159,7 +165,7 @@ def test_update_document( ] ) renderer = document_renderer.DocumentRenderer( - empty_polarion_worker.polarion_data_repo, model + empty_polarion_worker.polarion_data_repo, model, TEST_PROJECT_ID ) old_doc = polarion_api.Document( module_folder="_default", @@ -212,7 +218,7 @@ def test_mixed_authority_document( model: capellambse.MelodyModel, ): renderer = document_renderer.DocumentRenderer( - empty_polarion_worker.polarion_data_repo, model + empty_polarion_worker.polarion_data_repo, model, TEST_PROJECT_ID ) old_doc = polarion_api.Document( module_folder="_default", @@ -284,7 +290,7 @@ def test_create_full_authority_document_text_work_items( model: capellambse.MelodyModel, ): renderer = document_renderer.DocumentRenderer( - empty_polarion_worker.polarion_data_repo, model + empty_polarion_worker.polarion_data_repo, model, TEST_PROJECT_ID ) document_data = renderer.render_document( @@ -334,7 +340,7 @@ def test_update_full_authority_document_text_work_items( model: capellambse.MelodyModel, ): renderer = document_renderer.DocumentRenderer( - empty_polarion_worker.polarion_data_repo, model + empty_polarion_worker.polarion_data_repo, model, TEST_PROJECT_ID ) old_doc = polarion_api.Document( module_folder="_default", @@ -398,11 +404,20 @@ def test_render_all_documents_partially_successfully( model: capellambse.MelodyModel, caplog: pytest.LogCaptureFixture, ): + empty_polarion_worker.polarion_data_repo.update_work_items( + [ + dm.CapellaWorkItem( + "ATSY-1234", + uuid_capella="d8655737-39ab-4482-a934-ee847c7ff6bd", + type="componentExchange", + ), + ] + ) with open(TEST_COMBINED_DOCUMENT_CONFIG, "r", encoding="utf-8") as f: conf = document_config.read_config_file(f) renderer = document_renderer.DocumentRenderer( - empty_polarion_worker.polarion_data_repo, model + empty_polarion_worker.polarion_data_repo, model, TEST_PROJECT_ID ) projects_data = renderer.render_documents(conf, existing_documents()) @@ -434,6 +449,12 @@ def test_render_all_documents_partially_successfully( ) == 2 ) + assert ( + PROJECT_EXTERNAL_WORKITEM_SRC + in projects_data["TestProject"] + .updated_docs[0] + .document.home_page_content.value + ) assert ( len(projects_data[None].updated_docs[0].document.rendering_layouts) == 0 @@ -450,6 +471,56 @@ def test_render_all_documents_partially_successfully( ) +def test_insert_work_item_cross_project( + empty_polarion_worker: polarion_worker.CapellaPolarionWorker, + model: capellambse.MelodyModel, +): + empty_polarion_worker.polarion_data_repo.update_work_items( + [ + dm.CapellaWorkItem( + "ATSY-1234", + uuid_capella="d8655737-39ab-4482-a934-ee847c7ff6bd", + type="componentExchange", + ) + ] + ) + renderer = document_renderer.DocumentRenderer( + empty_polarion_worker.polarion_data_repo, model, TEST_PROJECT_ID + ) + + document_data_1 = renderer.render_document( + DOCUMENT_TEMPLATES, + DOCUMENT_WORK_ITEMS_CROSS_PROJECT, + "test", + "name", + "title", + document_project_id="DIFFERENT", + element="d8655737-39ab-4482-a934-ee847c7ff6bd", + ) + + document_data_2 = renderer.render_document( + DOCUMENT_TEMPLATES, + DOCUMENT_WORK_ITEMS_CROSS_PROJECT, + "test", + "name", + "title", + element="d8655737-39ab-4482-a934-ee847c7ff6bd", + ) + + content_1: list[html.HtmlElement] = html.fragments_fromstring( + document_data_1.document.home_page_content.value + ) + content_2: list[html.HtmlElement] = html.fragments_fromstring( + document_data_2.document.home_page_content.value + ) + + assert len(content_1) == 2 + assert content_1[0].attrib["id"].endswith(f"|project={TEST_PROJECT_ID}") + assert content_1[1].attrib["data-scope"] == TEST_PROJECT_ID + assert len(content_2) == 2 + assert content_2[0].attrib["id"].endswith("|external=true") + + def test_render_all_documents_overwrite_headings_layouts( empty_polarion_worker: polarion_worker.CapellaPolarionWorker, model: capellambse.MelodyModel, @@ -458,7 +529,11 @@ def test_render_all_documents_overwrite_headings_layouts( conf = document_config.read_config_file(f) renderer = document_renderer.DocumentRenderer( - empty_polarion_worker.polarion_data_repo, model, True, True + empty_polarion_worker.polarion_data_repo, + model, + TEST_PROJECT_ID, + True, + True, ) projects_data = renderer.render_documents(conf, existing_documents())