Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for documents in separate projects, status filters and text work items #106

Merged
merged 14 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions capella2polarion/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,10 @@ def render_documents(
overwrite_layouts,
)

new_documents, updated_documents, work_items = renderer.render_documents(
configs, documents
)

polarion_worker.post_documents(new_documents)
polarion_worker.update_documents(updated_documents)
polarion_worker.update_work_items(work_items)
projects_document_data = renderer.render_documents(configs, documents)
for project, project_data in projects_document_data.items():
polarion_worker.create_documents(project_data.new_docs, project)
polarion_worker.update_documents(project_data.updated_docs, project)


if __name__ == "__main__":
Expand Down
192 changes: 148 additions & 44 deletions capella2polarion/connectors/polarion_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
int: 0,
bool: False,
}
WORK_ITEMS_IN_PROJECT_QUERY = (
"SQL:(SELECT item.* FROM POLARION.WORKITEM item, POLARION.MODULE doc, "
"POLARION.PROJECT proj WHERE proj.C_ID = '{project}' AND "
"doc.FK_PROJECT = proj.C_PK AND doc.C_ID = '{doc_name}' AND "
"doc.C_MODULEFOLDER = '{doc_folder}' AND item.C_TYPE = '{wi_type}' AND "
"EXISTS (SELECT rel1.* FROM POLARION.REL_MODULE_WORKITEM rel1 WHERE "
"rel1.FK_URI_MODULE = doc.C_URI AND rel1.FK_URI_WORKITEM = item.C_URI))"
)
ewuerger marked this conversation as resolved.
Show resolved Hide resolved


class PolarionWorkerParams:
Expand Down Expand Up @@ -68,29 +76,53 @@ def __init__(
"Polarion PAT (Personal Access Token) parameter "
"is not a set properly."
)
self.client = polarion_api.OpenAPIPolarionProjectClient(
self.polarion_params.project_id,
self.polarion_params.delete_work_items,

self.polarion_client = polarion_api.PolarionClient(
polarion_api_endpoint=f"{self.polarion_params.url}/rest/v1",
polarion_access_token=self.polarion_params.private_access_token,
custom_work_item=data_models.CapellaWorkItem,
)
self.project_client = self.polarion_client.generate_project_client(
project_id=self.polarion_params.project_id,
delete_status=(
"deleted" if self.polarion_params.delete_work_items else None
),
add_work_item_checksum=True,
)
self._additional_clients: dict[str, polarion_api.ProjectClient] = {}
self.check_client()

def _get_client(
self, project_id: str | None
) -> polarion_api.ProjectClient:
if project_id is None:
return self.project_client
if project_id in self._additional_clients:
return self._additional_clients[project_id]
client = self.polarion_client.generate_project_client(
project_id=project_id,
delete_status=(
"deleted" if self.polarion_params.delete_work_items else None
),
add_work_item_checksum=True,
)
if not client.exists():
raise KeyError(f"Miss Polarion project with id {project_id}")
self._additional_clients[project_id] = client
return client

def check_client(self) -> None:
"""Instantiate the polarion client as member."""
if not self.client.project_exists():
if not self.project_client.exists():
raise KeyError(
"Miss Polarion project with id "
f"{self.polarion_params.project_id}"
)

def load_polarion_work_item_map(self):
"""Return a map from Capella UUIDs to Polarion work items."""
work_items = self.client.get_all_work_items(
work_items = self.project_client.work_items.get_all(
"HAS_VALUE:uuid_capella",
{"workitems": "id,uuid_capella,checksum,status,type"},
fields={"workitems": "id,uuid_capella,checksum,status,type"},
)
micha91 marked this conversation as resolved.
Show resolved Hide resolved
self.polarion_data_repo.update_work_items(work_items)

Expand All @@ -103,21 +135,19 @@ def delete_orphaned_work_items(
are marked as ``to be deleted`` via the status attribute.
"""

def serialize_for_delete(uuid: str) -> str:
work_item_id, _ = self.polarion_data_repo[uuid]
logger.info("Delete work item %r...", work_item_id)
return work_item_id

micha91 marked this conversation as resolved.
Show resolved Hide resolved
existing_work_items = {
uuid
for uuid, _, work_item in self.polarion_data_repo.items()
if work_item.status != "deleted"
}
uuids: set[str] = existing_work_items - set(converter_session)
work_item_ids = [serialize_for_delete(uuid) for uuid in uuids]
if work_item_ids:
work_items = [
self.polarion_data_repo.get_work_item_by_capella_uuid(uuid)
for uuid in uuids
]
if work_items:
micha91 marked this conversation as resolved.
Show resolved Hide resolved
try:
self.client.delete_work_items(work_item_ids)
self.project_client.work_items.delete(work_items)
self.polarion_data_repo.remove_work_items_by_capella_uuid(
uuids
)
Expand Down Expand Up @@ -146,7 +176,7 @@ def create_missing_work_items(
logger.info("Create work item for %r...", work_item.title)
if missing_work_items:
try:
self.client.create_work_items(missing_work_items)
self.project_client.work_items.create(missing_work_items)
self.polarion_data_repo.update_work_items(missing_work_items)
except polarion_api.PolarionApiException as error:
logger.error("Creating work items failed. %s", error.args[0])
Expand Down Expand Up @@ -184,18 +214,20 @@ def compare_and_update_work_item(
work_item_changed = new_work_item_check_sum != old_work_item_check_sum
try:
if work_item_changed or self.force_update:
old = self.client.get_work_item(old.id)
old = self.project_client.work_items.get(old.id)
micha91 marked this conversation as resolved.
Show resolved Hide resolved
if old.attachments:
old_attachments = (
self.client.get_all_work_item_attachments(
self.project_client.work_items.attachments.get_all(
work_item_id=old.id
)
)
else:
old_attachments = []
else:
old_attachments = self.client.get_all_work_item_attachments(
work_item_id=old.id
old_attachments = (
self.project_client.work_items.attachments.get_all(
work_item_id=old.id
)
)
if old_attachments or new.attachments:
work_item_changed |= self.update_attachments(
Expand All @@ -221,8 +253,8 @@ def compare_and_update_work_item(
del old.additional_attributes["uuid_capella"]

if old.linked_work_items_truncated:
old.linked_work_items = self.client.get_all_work_item_links(
old.id
old.linked_work_items = (
self.project_client.work_items.links.get_all(old.id)
)

# Type will only be updated, if set and should be used carefully
Expand Down Expand Up @@ -256,7 +288,7 @@ def compare_and_update_work_item(
new.title = None

try:
self.client.update_work_item(new)
self.project_client.work_items.update(new)
if delete_links:
id_list_str = ", ".join(delete_links.keys())
logger.info(
Expand All @@ -265,7 +297,9 @@ def compare_and_update_work_item(
new.type,
new.title,
)
self.client.delete_work_item_links(list(delete_links.values()))
self.project_client.work_items.links.delete(
list(delete_links.values())
)
ewuerger marked this conversation as resolved.
Show resolved Hide resolved

if create_links:
id_list_str = ", ".join(create_links.keys())
Expand All @@ -275,7 +309,9 @@ def compare_and_update_work_item(
new.type,
new.title,
)
self.client.create_work_item_links(list(create_links.values()))
self.project_client.work_items.links.create(
list(create_links.values())
)

except polarion_api.PolarionApiException as error:
logger.error(
Expand Down Expand Up @@ -346,12 +382,12 @@ def update_attachments(
attachment.file_name,
attachment.id,
)
self.client.delete_work_item_attachment(attachment)
self.project_client.work_items.attachments.delete(attachment)

old_attachment_file_names = set(old_attachment_dict)
new_attachment_file_names = set(new_attachment_dict)
for file_name in old_attachment_file_names - new_attachment_file_names:
self.client.delete_work_item_attachment(
self.project_client.work_items.attachments.delete(
old_attachment_dict[file_name]
)

Expand All @@ -361,7 +397,7 @@ def update_attachments(
new_attachment_file_names - old_attachment_file_names,
)
):
self.client.create_work_item_attachments(new_attachments)
self.project_client.work_items.attachments.create(new_attachments)
created = True

attachments_for_update = {}
Expand All @@ -386,7 +422,7 @@ def update_attachments(
):
continue

self.client.update_work_item_attachment(attachment)
self.project_client.work_items.attachments.update(attachment)
return created

@staticmethod
Expand Down Expand Up @@ -425,36 +461,104 @@ def compare_and_update_work_items(
if uuid in self.polarion_data_repo and data.work_item is not None:
self.compare_and_update_work_item(data)

def post_documents(self, documents: list[polarion_api.Document]):
def create_documents(
self,
document_datas: list[data_models.DocumentData],
document_project: str | None = None,
):
"""Create new documents."""
micha91 marked this conversation as resolved.
Show resolved Hide resolved
self.client.project_client.documents.create(documents)
client = self._get_client(document_project)
documents, _ = self._process_document_datas(client, document_datas)

client.documents.create(documents)

def update_documents(self, documents: list[polarion_api.Document]):
def update_documents(
self,
document_datas: list[data_models.DocumentData],
document_project: str | None = None,
):
"""Update existing documents."""
micha91 marked this conversation as resolved.
Show resolved Hide resolved
self.client.project_client.documents.update(documents)
client = self._get_client(document_project)
documents, headings = self._process_document_datas(
client, document_datas
)

client.work_items.update(headings)
client.documents.update(documents)

def _process_document_datas(
self,
client: polarion_api.ProjectClient,
document_datas: list[data_models.DocumentData],
):
documents = []
headings = []
micha91 marked this conversation as resolved.
Show resolved Hide resolved
for document_data in document_datas:
headings += document_data.headings
micha91 marked this conversation as resolved.
Show resolved Hide resolved
documents.append(document_data.document)
if document_data.text_work_item_provider.new_text_work_items:
self._create_and_update_text_work_items(
document_data.text_work_item_provider.new_text_work_items,
client,
)
document_data.text_work_item_provider.insert_text_work_items(
document_data.document,
)
return documents, headings

def get_document(
self, space: str, name: str
self, space: str, name: str, document_project: str | None = None
) -> polarion_api.Document | None:
"""Get a document from polarion and return None if not found."""
micha91 marked this conversation as resolved.
Show resolved Hide resolved
client = self._get_client(document_project)
try:
return self.client.project_client.documents.get(
return client.documents.get(
space, name, fields={"documents": "@all"}
)
except polarion_api.PolarionApiBaseException as e:
if e.args[0] == 404:
return None
raise e

def update_work_items(self, work_items: list[polarion_api.WorkItem]):
"""Update the given workitems without any additional checks."""
self.client.project_client.work_items.update(work_items)

def load_polarion_documents(
self, document_paths: t.Iterable[tuple[str, str]]
) -> dict[tuple[str, str], polarion_api.Document | None]:
"""Load the given document references from Polarion."""
self,
document_infos: t.Iterable[data_models.DocumentInfo],
) -> dict[
tuple[str | None, str, str],
tuple[polarion_api.Document | None, list[polarion_api.WorkItem]],
]:
ewuerger marked this conversation as resolved.
Show resolved Hide resolved
"""Load the documents referenced and text work items from Polarion."""
return {
(space, name): self.get_document(space, name)
for space, name in document_paths
(di.project_id, di.module_folder, di.module_name): (
self.get_document(
di.module_folder, di.module_name, di.project_id
),
ewuerger marked this conversation as resolved.
Show resolved Hide resolved
self._get_client(di.project_id).work_items.get_all(
WORK_ITEMS_IN_PROJECT_QUERY.format(
project=di.project_id
or self.polarion_params.project_id,
doc_folder=di.module_folder,
doc_name=di.module_name,
wi_type=di.text_work_item_type,
),
fields={"workitems": f"id,{di.text_work_item_id_field}"},
),
)
for di in document_infos
}

def _create_and_update_text_work_items(
self,
work_items: dict[str, polarion_api.WorkItem],
client: polarion_api.ProjectClient,
):
client.work_items.update(
[work_item for work_item in work_items.values() if work_item.id]
)
client.work_items.create(
[
work_item
for work_item in work_items.values()
if not work_item.id
]
)
15 changes: 13 additions & 2 deletions capella2polarion/converters/document_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import pydantic
import yaml

from capella2polarion import data_models
from capella2polarion.converters import polarion_html_helper

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -47,6 +48,10 @@ class BaseDocumentRenderingConfig(pydantic.BaseModel):
"""A template config, which can result in multiple Polarion documents."""

template_directory: str | pathlib.Path
project_id: str | None = None
text_work_item_type: str = polarion_html_helper.TEXT_WORK_ITEM_TYPE
text_work_item_id_field: str = polarion_html_helper.TEXT_WORK_ITEM_ID_FIELD
status_allow_list: list[str] | None = None
heading_numbering: bool = False
work_item_layouts: dict[str, WorkItemLayout] = pydantic.Field(
default_factory=dict
Expand Down Expand Up @@ -77,11 +82,17 @@ class DocumentConfigs(pydantic.BaseModel):
pydantic.Field(default_factory=list)
)

def iterate_documents(self) -> t.Iterator[tuple[str, str]]:
def iterate_documents(self) -> t.Iterator[data_models.DocumentInfo]:
"""Yield all document paths of the config as tuples."""
for conf in self.full_authority + self.mixed_authority:
for inst in conf.instances:
yield inst.polarion_space, inst.polarion_name
yield data_models.DocumentInfo(
project_id=conf.project_id,
module_folder=inst.polarion_space,
module_name=inst.polarion_name,
text_work_item_type=conf.text_work_item_type,
text_work_item_id_field=conf.text_work_item_id_field,
)


def read_config_file(
Expand Down
Loading
Loading