Skip to content

Commit

Permalink
Add jira --map-issue option
Browse files Browse the repository at this point in the history
  • Loading branch information
kkaarreell committed Nov 5, 2024
1 parent 6e6924d commit 29c21ae
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 46 deletions.
23 changes: 17 additions & 6 deletions newa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1170,7 +1170,7 @@ def connection_factory(self) -> jira.JIRA:
raise Exception('Could not authenticate to Jira. Wrong token?') from e
return conn

def newa_id(self, action: IssueAction, partial: bool = False) -> str:
def newa_id(self, action: Optional[IssueAction] = None, partial: bool = False) -> str:
"""
NEWA identifier
Expand All @@ -1179,6 +1179,9 @@ def newa_id(self, action: IssueAction, partial: bool = False) -> str:
respin. If 'partial' is defined it defines issues relevant for all respins.
"""

if not action:
return f"::: {IssueHandler.newa_label}"

if action.newa_id:
return f"::: {IssueHandler.newa_label} {action.newa_id}"
newa_id = f"::: {IssueHandler.newa_label} {action.id}: {self.artifact_job.id}"
Expand Down Expand Up @@ -1369,15 +1372,23 @@ def refresh_issue(self, action: IssueAction, issue: Issue) -> None:

issue_details = self.get_details(issue)
description = issue_details.fields.description
labels = issue_details.fields.labels
new_description = ""

# add NEWA label if missing
if self.newa_label not in labels:
issue_details.add_field_value('labels', self.newa_label)

# Issue does not have any NEWA ID - error.
if isinstance(description, str) and self.newa_id(action, True) not in description:
raise Exception(f"Issue {issue} is missing NEWA identifier!")
# Issue does not have any NEWA ID yet
if isinstance(description, str) and self.newa_id() not in description:
new_description = f"{self.newa_id(action)}\n{description}"

# Issue has NEWA ID but not the current respin - update it.
if isinstance(description, str) and self.newa_id(action) not in description:
new_description = re.sub(f"^{re.escape(self.newa_id(action, partial=True))}.*\n",
elif isinstance(description, str) and self.newa_id(action) not in description:
new_description = re.sub(f"^{re.escape(self.newa_id())}.*\n",
f"{self.newa_id(action)}\n", description)

if new_description:
try:
self.get_details(issue).update(fields={"description": new_description})
self.comment_issue(
Expand Down
110 changes: 70 additions & 40 deletions newa/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,13 @@ def apply_mapping(string: str,
'--issue-config',
help='Specifies path to a Jira issue configuration file.',
)
@click.option(
'--map-issue',
default=[],
multiple=True,
help=('Map issue id from the issue-config file to an existing Jira issue. '
'Example: --map-issue jira_epic=RHEL-123456'),
)
@click.option(
'--recreate',
is_flag=True,
Expand Down Expand Up @@ -422,6 +429,7 @@ def apply_mapping(string: str,
def cmd_jira(
ctx: CLIContext,
issue_config: str,
map_issue: list[str],
recreate: bool,
issue: str,
job_recipe: str,
Expand All @@ -448,6 +456,15 @@ def _jira_fake_id_generator() -> Generator[str, int, None]:

jira_none_id = _jira_fake_id_generator()

# load issue mapping specified on a command line
issue_mapping = {}
for m in map_issue:
r = re.fullmatch(r'([^\s=]+)=([^=]*)', m)
if not r:
raise Exception(f"Mapping {m} does not having expected format 'key=value'")
key, value = r.groups()
issue_mapping[key] = value

for artifact_job in ctx.load_artifact_jobs('event-'):
# when issue_config is defined, --issue and --job-recipe are ignored
# as it will be set depending on the --issue-config content
Expand Down Expand Up @@ -531,55 +548,69 @@ def _jira_fake_id_generator() -> Generator[str, int, None]:
queue_length = len(issue_actions)
last_queue_length = endless_loop_check.get(action.id, 0)
if last_queue_length == queue_length:
raise Exception(f"Parent {action.parent_id} for {action.id} not found!")
raise Exception(f"Parent {action.parent_id} for {action.id} not found!"
"It does not exists or is closed.")

endless_loop_check[action.id] = queue_length
ctx.logger.info(f"Skipped for now (parent {action.parent_id} not yet found)")

issue_actions.append(action)
continue

# Find existing issues related to artifact_job and action
# If we are supposed to recreate closed issues, search only for opened ones
if recreate:
search_result = jira_handler.get_related_issues(
action, all_respins=True, closed=False)
else:
search_result = jira_handler.get_related_issues(
action, all_respins=True, closed=True)

# Issues related to the curent respin and previous one(s).
new_issues: list[Issue] = []
old_issues: list[Issue] = []
for jira_issue_key, jira_issue in search_result.items():
ctx.logger.info(f"Checking {jira_issue_key}")

# In general, issue is new (relevant to the current respin) if it has newa_id
# of this action in the description. Otherwise, it is old (relevant to the
# previous respins).
#
# However, it might happen that we encounter an issue that is new but its
# original parent has been replaced by a newly created issue. In such a case
# we have to re-create the issue as well and drop the old one.
is_new = False
if jira_handler.newa_id(action) in jira_issue["description"] \
and (not action.parent_id
or action.parent_id not in created_action_ids):
is_new = True

if is_new:
new_issues.append(
Issue(
jira_issue_key,
group=config.group,
closed=jira_issue["status"] == "closed"))
# opened old issues may be reused
elif jira_issue["status"] == "opened":
old_issues.append(
Issue(
jira_issue_key,
group=config.group,
closed=False))

# first check if we have a match in issue_mapping
if action.id and action.id in issue_mapping and issue_mapping[action.id].strip():
mapped_issue = Issue(
issue_mapping[action.id].strip(),
group=config.group)
jira_issue = jira_handler.get_details(mapped_issue)
mapped_issue.closed = jira_issue.get_field(
"status").name in jira_handler.transitions['closed']
new_issues.append(mapped_issue)

# otherwise we need to search for the issue in Jira
else:
# Find existing issues related to artifact_job and action
# If we are supposed to recreate closed issues, search only for opened ones
if recreate:
search_result = jira_handler.get_related_issues(
action, all_respins=True, closed=False)
else:
search_result = jira_handler.get_related_issues(
action, all_respins=True, closed=True)

for jira_issue_key, jira_issue in search_result.items():
ctx.logger.info(f"Checking {jira_issue_key}")

# In general, issue is new (relevant to the current respin) if it has
# newa_id of this action in the description. Otherwise, it is old
# (relevant to the previous respins).
# However, it might happen that we encounter an issue that is new but
# its original parent has been replaced by a newly created issue.
# In such a case we have to re-create the issue as well and drop the
# old one.
is_new = False
if jira_handler.newa_id(action) in jira_issue["description"] \
and (not action.parent_id
or action.parent_id not in created_action_ids):
is_new = True

if is_new:
new_issues.append(
Issue(
jira_issue_key,
group=config.group,
closed=jira_issue["status"] == "closed"))
# opened old issues may be reused
elif jira_issue["status"] == "opened":
old_issues.append(
Issue(
jira_issue_key,
group=config.group,
closed=False))

# Old opened issue(s) can be re-used for the current respin.
if old_issues and action.on_respin == OnRespinAction.KEEP:
Expand Down Expand Up @@ -629,7 +660,6 @@ def _jira_fake_id_generator() -> Generator[str, int, None]:
processed_actions[action.id] = new_issue

# If the old issue was reused, re-fresh it.
parent = processed_actions[action.parent_id] if action.parent_id else None
jira_handler.refresh_issue(action, new_issue)
ctx.logger.info(f"Issue {new_issue} re-used")

Expand Down

0 comments on commit 29c21ae

Please sign in to comment.