Skip to content

Commit

Permalink
merge: Merge pull request #85 from DSD-DBS/additional-document-features
Browse files Browse the repository at this point in the history
Mixed Authority Mode for Document Generation and small additional features
  • Loading branch information
ewuerger authored Aug 1, 2024
2 parents 1892da9 + fb91478 commit ec1aa29
Show file tree
Hide file tree
Showing 16 changed files with 823 additions and 87 deletions.
77 changes: 58 additions & 19 deletions capella2polarion/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,14 @@ def synchronize(
type=click.File(mode="r", encoding="utf8"),
default=None,
)
@click.option("--overwrite-layouts", is_flag=True, default=False)
@click.option("--overwrite-numbering", is_flag=True, default=False)
@click.pass_context
def render_documents(
ctx: click.core.Context,
document_rendering_config: typing.TextIO,
overwrite_layouts: bool,
overwrite_numbering: bool,
) -> None:
"""Call this command to render documents based on a config file."""
capella_to_polarion_cli: Capella2PolarionCli = ctx.obj
Expand All @@ -151,37 +155,72 @@ def render_documents(

polarion_worker.load_polarion_work_item_map()

configs = document_config.read_config_file(document_rendering_config)
configs = document_config.read_config_file(
document_rendering_config, capella_to_polarion_cli.capella_model
)
renderer = document_renderer.DocumentRenderer(
polarion_worker.polarion_data_repo,
capella_to_polarion_cli.capella_model,
)
for c in configs:
for inst in c.instances:
old_doc = polarion_worker.get_document(
inst.polarion_space, inst.polarion_name
)
if old_doc:
old_doc.title = inst.polarion_title
new_doc, wis = renderer.render_document(
c.template_directory,
c.template,
for config in configs.full_authority:
rendering_layouts = document_config.generate_work_item_layouts(
config.work_item_layouts
)
for instance in config.instances:
if old_doc := polarion_worker.get_and_customize_document(
instance.polarion_space,
instance.polarion_name,
instance.polarion_title,
rendering_layouts if overwrite_layouts else None,
config.heading_numbering if overwrite_numbering else None,
):
new_doc, work_items = renderer.render_document(
config.template_directory,
config.template,
document=old_doc,
**inst.params,
**instance.params,
)
polarion_worker.update_document(new_doc)
polarion_worker.update_work_items(wis)
polarion_worker.update_work_items(work_items)
else:
new_doc, _ = renderer.render_document(
c.template_directory,
c.template,
inst.polarion_space,
inst.polarion_name,
inst.polarion_title,
**inst.params,
config.template_directory,
config.template,
instance.polarion_space,
instance.polarion_name,
instance.polarion_title,
config.heading_numbering,
rendering_layouts,
**instance.params,
)
polarion_worker.post_document(new_doc)

for config in configs.mixed_authority:
rendering_layouts = document_config.generate_work_item_layouts(
config.work_item_layouts
)
for instance in config.instances:
old_doc = polarion_worker.get_and_customize_document(
instance.polarion_space,
instance.polarion_name,
instance.polarion_title,
rendering_layouts if overwrite_layouts else None,
config.heading_numbering if overwrite_numbering else None,
)
assert old_doc is not None, (
"Did not find document "
f"{instance.polarion_space}/{instance.polarion_name}"
)
new_doc, work_items = renderer.update_mixed_authority_document(
old_doc,
config.template_directory,
config.sections,
instance.params,
instance.section_params,
)
polarion_worker.update_document(new_doc)
polarion_worker.update_work_items(work_items)


if __name__ == "__main__":
cli(obj={})
18 changes: 18 additions & 0 deletions capella2polarion/connectors/polarion_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,24 @@ def get_document(
return None
raise e

def get_and_customize_document(
self,
space: str,
name: str,
new_title: str | None,
rendering_layouts: list[polarion_api.RenderingLayout] | None,
heading_numbering: bool | None,
) -> polarion_api.Document | None:
"""Get a document from polarion and return None if not found."""
if document := self.get_document(space, name):
document.title = new_title
if rendering_layouts is not None:
document.rendering_layouts = rendering_layouts
if heading_numbering is not None:
document.outline_numbering = heading_numbering

return document

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)
109 changes: 104 additions & 5 deletions capella2polarion/converters/document_config.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
# Copyright DB InfraGO AG and contributors
# SPDX-License-Identifier: Apache-2.0
"""Module with classes and a loader for document rendering configs."""
import logging
import pathlib
import typing as t

import capellambse
import jinja2
import polarion_rest_api_client as polarion_api
import pydantic
import yaml

from capella2polarion.converters import polarion_html_helper

logger = logging.getLogger(__name__)


class WorkItemLayout(pydantic.BaseModel):
"""Configuration for rendering layouts of work items."""

show_description: bool = True
show_title: bool = True
show_fields_as_table: bool = True
fields_at_start: list[str] = pydantic.Field(default_factory=list)
fields_at_end: list[str] = pydantic.Field(default_factory=list)


class DocumentRenderingInstance(pydantic.BaseModel):
"""An instance of a document that should be created in Polarion."""
Expand All @@ -17,15 +35,96 @@ class DocumentRenderingInstance(pydantic.BaseModel):
params: dict[str, t.Any] = pydantic.Field(default_factory=dict)


class DocumentRenderingConfig(pydantic.BaseModel):
class SectionBasedDocumentRenderingInstance(DocumentRenderingInstance):
"""An instance of a mixed authority doc with section specific params."""

section_params: dict[str, dict[str, t.Any]] = pydantic.Field(
default_factory=dict
)


class BaseDocumentRenderingConfig(pydantic.BaseModel):
"""A template config, which can result in multiple Polarion documents."""

template_directory: str | pathlib.Path
template: str
heading_numbering: bool = False
work_item_layouts: dict[str, WorkItemLayout] = pydantic.Field(
default_factory=dict
)
instances: list[DocumentRenderingInstance]


def read_config_file(config: t.TextIO):
class FullAuthorityDocumentRenderingConfig(BaseDocumentRenderingConfig):
"""Full authority document config with one template per document."""

template: str


class MixedAuthorityDocumentRenderingConfig(BaseDocumentRenderingConfig):
"""Mixed authority document with multiple auto generated sections."""

sections: dict[str, str]
instances: list[SectionBasedDocumentRenderingInstance]


class DocumentConfigs(pydantic.BaseModel):
"""The overall document configuration repository."""

full_authority: list[FullAuthorityDocumentRenderingConfig] = (
pydantic.Field(default_factory=list)
)
mixed_authority: list[MixedAuthorityDocumentRenderingConfig] = (
pydantic.Field(default_factory=list)
)


def read_config_file(
config: t.TextIO, model: capellambse.MelodyModel | None = None
) -> DocumentConfigs:
"""Read a yaml containing a list of DocumentRenderingConfigs."""
config_content = yaml.safe_load(config)
return [DocumentRenderingConfig(**c) for c in config_content]
if config.name.endswith(".j2"):
assert model is not None, "For jinja configs the model is mandatory"
template = jinja2.Template(config.read())
config_content = yaml.safe_load(template.render(model=model))
else:
config_content = yaml.safe_load(config)
if isinstance(config_content, list):
config_content = {"full_authority": config_content}
return DocumentConfigs(**config_content)


def generate_work_item_layouts(
configs: dict[str, WorkItemLayout]
) -> list[polarion_api.RenderingLayout]:
"""Create polarion_api.RenderingLayouts for a given configuration."""
results = []
for _type, conf in configs.items():
if conf.show_title and conf.show_description:
layouter = polarion_api.data_models.Layouter.SECTION
elif conf.show_description:
layouter = polarion_api.data_models.Layouter.PARAGRAPH
else:
if not conf.show_title:
logger.warning(
"Either the title or the description must be shown."
"For that reason, the title will be shown for %s.",
_type,
)
layouter = polarion_api.data_models.Layouter.TITLE
results.append(
polarion_api.RenderingLayout(
type=_type,
layouter=layouter,
label=polarion_html_helper.camel_case_to_words(_type),
properties=polarion_api.data_models.RenderingProperties(
fields_at_start=conf.fields_at_start,
fields_at_end=conf.fields_at_end,
fields_at_end_as_table=conf.show_fields_as_table,
hidden=True,
sidebar_work_item_fields=conf.fields_at_start
+ conf.fields_at_end,
),
)
)

return results
Loading

0 comments on commit ec1aa29

Please sign in to comment.