Skip to content

Commit

Permalink
Move linking to utils.jira and use ModuleImporter
Browse files Browse the repository at this point in the history
  • Loading branch information
tkoscieln committed Sep 24, 2024
1 parent 8c41c46 commit 602250e
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 108 deletions.
7 changes: 4 additions & 3 deletions tests/unit/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import tmt.plugins
import tmt.steps.discover
import tmt.utils
import tmt.utils.jira
from tmt.log import Logger
from tmt.utils import (
Command,
Expand Down Expand Up @@ -1730,7 +1731,7 @@ def tearDown(self):
def test_jira_link_test_only(self, mock_config_tree, mock_add_simple_link) -> None:
mock_config_tree.return_value.fmf_tree = self.tree
test = tmt.Tree(logger=self.logger, path=Path(".")).tests(names=['tmp/test'])[0]
tmt.utils.jira_link(
tmt.utils.jira.jira_link(
[test], tmt.base.Links(
data=['verifies:issues.redhat.com/browse/TT-262']), self.logger)
result = mock_add_simple_link.call_args.args[1]
Expand All @@ -1745,7 +1746,7 @@ def test_jira_link_test_plan_story(self, mock_config_tree, mock_add_simple_link)
test = tmt.Tree(logger=self.logger, path=Path(".")).tests(names=['tmp/test'])[0]
plan = tmt.Tree(logger=self.logger, path=Path(".")).plans(names=['tmp'])[0]
story = tmt.Tree(logger=self.logger, path=Path(".")).stories(names=['tmp'])[0]
tmt.utils.jira_link([test, plan, story], tmt.base.Links(
tmt.utils.jira.jira_link([test, plan, story], tmt.base.Links(
data=['verifies:issues.redhat.com/browse/TT-262']), self.logger)
result = mock_add_simple_link.call_args.args[1]
assert (
Expand All @@ -1760,7 +1761,7 @@ def test_jira_link_test_plan_story(self, mock_config_tree, mock_add_simple_link)
def test_create_link_relation(self, mock_config_tree, mock_add_simple_link) -> None:
mock_config_tree.return_value.fmf_tree = self.tree
test = tmt.Tree(logger=self.logger, path=Path(".")).tests(names=['tmp/test'])[0]
tmt.utils.jira_link(
tmt.utils.jira.jira_link(
[test], tmt.base.Links(
data=['verifies:issues.redhat.com/browse/TT-262']), self.logger)
# Load the test object again with the link present
Expand Down
7 changes: 4 additions & 3 deletions tmt/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import tmt.templates
import tmt.utils
import tmt.utils.git
import tmt.utils.jira
from tmt.checks import Check
from tmt.lint import LinterOutcome, LinterReturn
from tmt.result import Result
Expand Down Expand Up @@ -1297,7 +1298,7 @@ def _get_template_content(template: str, template_type: str) -> str:
logger=logger).tests(
names=[directory_path.name],
apply_command_line=False)
tmt.utils.jira_link(nodes=tests, links=links, logger=logger)
tmt.utils.jira.jira_link(nodes=tests, links=links, logger=logger)

@property
def manual_test_path(self) -> Path:
Expand Down Expand Up @@ -2032,7 +2033,7 @@ def create(
logger=logger).plans(
names=[directory_path.name],
apply_command_line=False)
tmt.utils.jira_link(nodes=plans, links=links, logger=logger)
tmt.utils.jira.jira_link(nodes=plans, links=links, logger=logger)

def _iter_steps(self,
enabled_only: bool = True,
Expand Down Expand Up @@ -2754,7 +2755,7 @@ def create(
logger=logger).stories(
names=[directory_path.name],
apply_command_line=False)
tmt.utils.jira_link(nodes=stories, links=links, logger=logger)
tmt.utils.jira.jira_link(nodes=stories, links=links, logger=logger)

@staticmethod
def overview(tree: 'Tree') -> None:
Expand Down
3 changes: 2 additions & 1 deletion tmt/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import tmt.templates
import tmt.trying
import tmt.utils
import tmt.utils.jira
import tmt.utils.rest
from tmt.options import Deprecated, create_options_decorator, option
from tmt.utils import Command, Path
Expand Down Expand Up @@ -2262,4 +2263,4 @@ def link(context: Context,
nodes.extend(context.obj.tree.plans(names=[name]))
if context.obj.tree.stories(names=[name]):
nodes.extend(context.obj.tree.stories(names=[name]))
tmt.utils.jira_link(nodes, tmt.base.Links(data=link), context.obj.logger, separate)
tmt.utils.jira.jira_link(nodes, tmt.base.Links(data=link), context.obj.logger, separate)
101 changes: 0 additions & 101 deletions tmt/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,13 @@
from tmt.log import LoggableValue

if TYPE_CHECKING:
from _typeshed import DataclassInstance

import tmt.base
import tmt.cli
import tmt.options
import tmt.steps
from tmt._compat.typing import Self, TypeAlias


JIRA: Any = None


def configure_optional_constant(default: Optional[int], envvar: str) -> Optional[int]:
"""
Deduce the actual value of a global constant which may be left unset.
Expand Down Expand Up @@ -5949,99 +5944,3 @@ def is_url(url: str) -> bool:
""" Check if the given string is a valid URL """
parsed = urllib.parse.urlparse(url)
return bool(parsed.scheme and parsed.netloc)


def import_jira() -> None:
""" Import Python Jira library """
try:
global JIRA
from jira import JIRA
except ImportError:
raise GeneralError(
"Install 'tmt+link-jira' to use the Jira linking.")


def jira_link(
nodes: Sequence[Union['tmt.base.Test', 'tmt.base.Plan', 'tmt.base.Story']],
links: 'tmt.base.Links',
logger: tmt.log.Logger,
separate: bool = False) -> None:
""" Link the object to Jira issue and create the URL to tmt web service """
import_jira()

def create_url_params(tmt_object: tmt.base.Core) -> dict[str, Any]:
tmt_type = tmt_object.__class__.__name__.lower()
fmf_id = tmt_object.fmf_id

url_params: dict[str, Any] = {
'format': 'html',
f'{tmt_type}-url': fmf_id.url,
f'{tmt_type}-name': fmf_id.name
}

if fmf_id.path and isinstance(fmf_id.git_root, Path):
fmf_path = fmf_id.path.relative_to(fmf_id.git_root).resolve().as_posix()
fmf_path = fmf_path.removeprefix(fmf_id.git_root.as_posix())
url_params[f'{tmt_type}-path'] = fmf_path.lstrip('/')

if fmf_id.ref:
url_params[f'{tmt_type}-ref'] = fmf_id.ref

return url_params

def create_url(baseurl: str, url_params: dict[str, str]) -> str:
return urllib.parse.urljoin(baseurl, '?' + urllib.parse.urlencode(url_params))

logger.print(
f'Linking {fmf.utils.listed([type(node).__name__.lower() for node in nodes])}'
f' to Jira issue.')

# Setup config tree
config_tree = tmt.utils.Config()
# Linking is not setup in config, therefore user does not want to use linking
linking_node = config_tree.fmf_tree.find('/user/linking')
if not linking_node and linking_node.data.get('linking') is None:
return
linking_config = linking_node.data.get('issue-tracker')[0]
verifies = links.get('verifies')[0]
target = verifies.to_dict()['target']
# Parse the target url
issue_id = target.split('/')[-1]
jira = JIRA(server=linking_config['url'], token_auth=linking_config['token'])
link_object: dict[str, str] = {}
service_url: dict[str, str] = {}
for node in nodes:
# Try to add the link relation to object's data if it is not already there
with node.node as data:
link_relation = {"verifies": target}
if 'link' in data:
if link_relation not in data['link']:
logger.print('Adding linking to the metadata.')
data['link'].append(link_relation)
else:
logger.print('Linking already exists in the object data, skipping this step.')
else:
logger.print('Adding linking to the metadata.')
data['link'] = [link_relation]
# Single object in list of nodes = creating new object
# or linking multiple existing separately
if len(nodes) == 1 or (len(nodes) > 1 and separate):
service_url = create_url_params(tmt_object=node)
link_object = {
"url": create_url(linking_config["tmt-web-url"], service_url),
"title": f'[tmt_web] Metadata of the {type(node).__name__.lower()}'
f' covering this issue'}
jira.add_simple_link(issue_id, link_object)
logger.print(f'Link added to issue {target}.')
if len(nodes) > 1 and not separate:
url_part = create_url_params(tmt_object=node)
service_url.update(url_part)
link_object = {
"url": create_url(linking_config["tmt-web-url"], service_url),
"title": f'[tmt_web] Metadata of the'
f' {fmf.utils.listed([type(node).__name__.lower() for node in nodes])}'
f' covering this issue'}
if len(nodes) > 1 and not separate:
# Send request to JIRA when len(nodes) > 1 and not separate, after all nodes were processed
jira.add_simple_link(issue_id, link_object)
logger.print(f'Link added to issue {target}.')
103 changes: 103 additions & 0 deletions tmt/utils/jira.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import urllib.parse
from collections.abc import Sequence
from typing import Any, Union

import fmf.utils

import tmt.base
import tmt.log
import tmt.utils
from tmt._compat.pathlib import Path
from tmt.plugins import ModuleImporter

import_jira: ModuleImporter['jira'] = ModuleImporter( # type: ignore[unused-ignore]
'jira',
tmt.utils.ReportError,
"Install 'tmt+link-jira' to use the Jira linking.",
tmt.log.Logger.get_bootstrap_logger())


def jira_link(
nodes: Sequence[Union['tmt.base.Test', 'tmt.base.Plan', 'tmt.base.Story']],
links: 'tmt.base.Links',
logger: tmt.log.Logger,
separate: bool = False) -> None:
""" Link the object to Jira issue and create the URL to tmt web service """
jira_module = import_jira()

def create_url_params(tmt_object: tmt.base.Core) -> dict[str, Any]:
tmt_type = tmt_object.__class__.__name__.lower()
fmf_id = tmt_object.fmf_id

url_params: dict[str, Any] = {
'format': 'html',
f'{tmt_type}-url': fmf_id.url,
f'{tmt_type}-name': fmf_id.name
}

if fmf_id.path and isinstance(fmf_id.git_root, Path):
fmf_path = fmf_id.path.relative_to(fmf_id.git_root).resolve().as_posix()
fmf_path = fmf_path.removeprefix(fmf_id.git_root.as_posix())
url_params[f'{tmt_type}-path'] = fmf_path.lstrip('/')

if fmf_id.ref:
url_params[f'{tmt_type}-ref'] = fmf_id.ref

return url_params

def create_url(baseurl: str, url_params: dict[str, str]) -> str:
return urllib.parse.urljoin(baseurl, '?' + urllib.parse.urlencode(url_params))

logger.print(
f'Linking {fmf.utils.listed([type(node).__name__.lower() for node in nodes])}'
f' to Jira issue.')

# Setup config tree
config_tree = tmt.utils.Config()
# Linking is not setup in config, therefore user does not want to use linking
linking_node = config_tree.fmf_tree.find('/user/linking')
if not linking_node and linking_node.data.get('linking') is None:
return
linking_config = linking_node.data.get('issue-tracker')[0]
verifies = links.get('verifies')[0]
target = verifies.to_dict()['target']
# Parse the target url
issue_id = target.split('/')[-1]
jira = jira_module.JIRA(server=linking_config['url'], token_auth=linking_config['token'])
link_object: dict[str, str] = {}
service_url: dict[str, str] = {}
for node in nodes:
# Try to add the link relation to object's data if it is not already there
with node.node as data:
link_relation = {"verifies": target}
if 'link' in data:
if link_relation not in data['link']:
logger.print('Adding linking to the metadata.')
data['link'].append(link_relation)
else:
logger.print('Linking already exists in the object data, skipping this step.')
else:
logger.print('Adding linking to the metadata.')
data['link'] = [link_relation]
# Single object in list of nodes = creating new object
# or linking multiple existing separately
if len(nodes) == 1 or (len(nodes) > 1 and separate):
service_url = create_url_params(tmt_object=node)
link_object = {
"url": create_url(linking_config["tmt-web-url"], service_url),
"title": f'[tmt_web] Metadata of the {type(node).__name__.lower()}'
f' covering this issue'}
jira.add_simple_link(issue_id, link_object)
logger.print(f'Link added to issue {target}.')
if len(nodes) > 1 and not separate:
url_part = create_url_params(tmt_object=node)
service_url.update(url_part)
link_object = {
"url": create_url(linking_config["tmt-web-url"], service_url),
"title": f'[tmt_web] Metadata of the'
f' {fmf.utils.listed([type(node).__name__.lower() for node in nodes])}'
f' covering this issue'}
if len(nodes) > 1 and not separate:
# Send request to JIRA when len(nodes) > 1 and not separate, after all nodes were processed
jira.add_simple_link(issue_id, link_object)
logger.print(f'Link added to issue {target}.')

0 comments on commit 602250e

Please sign in to comment.