Skip to content

Commit

Permalink
fix: Check for visible changes to diagrams
Browse files Browse the repository at this point in the history
  • Loading branch information
huyenngn committed Sep 28, 2023
1 parent 083065b commit b80df6e
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 1 deletion.
44 changes: 44 additions & 0 deletions capella2polarion/elements/api_helper.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# Copyright DB Netz AG and contributors
# SPDX-License-Identifier: Apache-2.0
"""Capella2Polarion specific helper functions to use the API."""
import base64
import collections.abc as cabc
import io
import logging
import re
import typing as t

import polarion_rest_api_client as polarion_api
from capellambse.model import common
from PIL import Image, ImageChops

from capella2polarion.elements import serialize

Expand Down Expand Up @@ -41,6 +45,12 @@ def patch_work_item(
if new := receiver(obj, ctx):
wid = ctx["POLARION_ID_MAP"][obj.uuid]
old: serialize.CapellaWorkItem = ctx["POLARION_WI_MAP"][obj.uuid]

if _type == "diagram" and has_visual_changes(
old.description, new.description
):
return

if new == old:
return

Expand Down Expand Up @@ -72,6 +82,40 @@ def patch_work_item(
logger.error("Updating work item %r failed. %s", wi, error.args[0])


def decode_diagram(dia: str):
"""Decode a diagram from a base64 string."""
encoded = dia.replace("data:image/", "").split(";base64,", 1)

decoded = base64.b64decode(encoded[1])

return encoded[0], decoded


def has_visual_changes(old: str, new: str) -> bool:
"""Return True if the images of the diagrams differ."""
type_old, decoded_old = decode_diagram(old)
type_new, decoded_new = decode_diagram(new)

if type_old != type_new:
return True

if type_old == "svg+xml":
d_new = decoded_new.decode("utf-8").splitlines()
for i, d_old in enumerate(decoded_old.decode("utf-8").splitlines()):
if re.sub(r'id=["\'][^"\']*["\']', "", d_old) != re.sub(
r'id=["\'][^"\']*["\']', "", d_new[i]
):
return True
return False

image_old = Image.open(io.BytesIO(decoded_old))
image_new = Image.open(io.BytesIO(decoded_new))

diff = ImageChops.difference(image_old, image_new)

return bool(diff.getbbox())


def handle_links(
left: cabc.Iterable[polarion_api.WorkItemLink],
right: cabc.Iterable[polarion_api.WorkItemLink],
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ dependencies = [
"PyYAML",
"polarion-rest-api-client @ git+https://github.com/DSD-DBS/polarion-rest-api-client.git@feat-add-attachments-workitem-relations",
"requests",
"jinja2",
]

[project.urls]
Expand Down
3 changes: 3 additions & 0 deletions tests/data/svg_diff/example.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions tests/data/svg_diff/example.svg.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Copyright DB Netz AG and contributors
SPDX-License-Identifier: Apache-2.0
124 changes: 123 additions & 1 deletion tests/test_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,29 @@

from __future__ import annotations

import base64
import io
import logging
import pathlib
import typing as t
from unittest import mock

import capellambse
import jinja2
import markupsafe
import polarion_rest_api_client as polarion_api
import pytest
from capellambse.model import common
from PIL import Image

from capella2polarion import elements
from capella2polarion.elements import diagram, element, helpers, serialize
from capella2polarion.elements import (
api_helper,
diagram,
element,
helpers,
serialize,
)

# pylint: disable-next=relative-beyond-top-level, useless-suppression
from .conftest import TEST_DIAGRAM_CACHE, TEST_HOST # type: ignore[import]
Expand Down Expand Up @@ -64,6 +75,117 @@
)


class TestAPIHelper:
SVG_PATH = (
pathlib.Path(__file__).parent / "data" / "svg_diff" / "example.svg"
)
SVG_PREFIX = "data:image/svg+xml;base64,"
PNG_PREFIX = "data:image/png;base64,"

def encode_png(self, img: Image.Image) -> str:
image_buffer = io.BytesIO()
img.save(image_buffer, format="PNG")
image_buffer.seek(0)
encoded = base64.b64encode(image_buffer.read()).decode()
image_buffer.close()
return f"{self.PNG_PREFIX}{encoded}"

def test_pixel_diff(self):
img1 = Image.new("RGB", (100, 100))
img2 = Image.new("RGB", (100, 100))
img2.putpixel((0, 0), (255, 0, 0))

assert (
api_helper.has_visual_changes(
self.encode_png(img1),
self.encode_png(img2),
)
is True
)

assert (
api_helper.has_visual_changes(
self.encode_png(img1),
self.encode_png(img1),
)
is False
)

def encode_svg(self, render_params, params) -> str:
svg = jinja2.environment.Template(self.SVG_PATH.read_text()).render(
**render_params, **params
)
encoded = base64.b64encode(svg.encode("utf-8")).decode("utf-8")
return f"{self.SVG_PREFIX}{encoded}"

@pytest.mark.parametrize(
"render_params,changed_params,expected",
[
pytest.param(
{
"id": "test",
"width": "100",
"height": "100",
"x": "0",
"y": "0",
"fill": "blue",
},
({}, {}),
False,
id="unchanged",
),
pytest.param(
{
"id": "test",
"width": "100",
"height": "100",
"x": "0",
"y": "0",
},
({"fill": "red"}, {"fill": "blue"}),
True,
id="fill_changed",
),
pytest.param(
{
"width": "100",
"height": "100",
"x": "0",
"y": "0",
"fill": "blue",
},
({"id": "test"}, {"id": "test2"}),
False,
id="id_changed",
),
pytest.param(
{
"width": "100",
"height": "100",
"x": "0",
"y": "0",
},
(
{"id": "test", "fill": "blue"},
{"id": "test2", "fill": "red"},
),
True,
id="id_and_fill_changed",
),
],
)
def test_svg_diff(self, render_params, changed_params, expected):
old_params, new_params = changed_params

assert (
api_helper.has_visual_changes(
self.encode_svg(render_params, old_params),
self.encode_svg(render_params, new_params),
)
is expected
)


class TestDiagramElements:
@staticmethod
@pytest.fixture
Expand Down

0 comments on commit b80df6e

Please sign in to comment.