Skip to content

Commit

Permalink
added update4d jira.py with new method + docs +example
Browse files Browse the repository at this point in the history
  • Loading branch information
gkowalc committed Mar 19, 2024
1 parent e8e8959 commit 8c2824b
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 29 deletions.
64 changes: 35 additions & 29 deletions atlassian/jira.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import logging
import re
import os
from sys import setrecursionlimit
from warnings import warn
from deprecated import deprecated
from requests import HTTPError
Expand Down Expand Up @@ -1706,34 +1705,41 @@ def get_issue_remote_links(self, issue_key, global_id=None, internal_id=None):
url += "/" + internal_id
return self.get(url, params=params)

def get_issue_tree_recursive(self, issue_key, tree=[], depth=0, depth_max=50):
'''
:param jira issue_key:
:param tree: blank parameter used for recursion. Don't add anything to it when calling the function.
:param depth: blank parameter used for recursion. Don't add anything to it when calling the function.
:param depth_max: maximum depth of recursion if the tree is too big
:return: list that contains the tree of the issue, with all subtasks and inward linked issues
example of the output get_issue_tree(INTEGRTEST-2): [{'INTEGRTEST-2': 'TEST-1'}, {'INTEGRTEST-2': 'INTEGRTEST-3'}, {'INTEGRTEST-2': 'INTEGRTEST-4'}, {'INTEGRTEST-4': 'INTEGRTEST-6'}]
'''
# Check the recursion depth
if depth > depth_max:
raise Exception("Recursion depth exceeded")
issue = self.get_issue(issue_key)
issue_links = issue['fields']['issuelinks']
subtasks = issue['fields']['subtasks']
for issue_link in issue_links:
if issue_link.get('inwardIssue') is not None:
parent_issue_key = issue['key']
if not [x for x in tree if issue_link['inwardIssue']['key'] in x.keys()]: # condition to avoid infinite recursion
tree.append({parent_issue_key: issue_link['inwardIssue']['key']})
self.get_issue_tree(issue_link['inwardIssue']['key'], tree, depth + 1) # recursive call of the function
for subtask in subtasks:
if subtask.get('key') is not None:
parent_issue_key = issue['key']
if not [x for x in tree if subtask['key'] in x.keys()]: # condition to avoid infinite recursion
tree.append({parent_issue_key: subtask['key']})
self.get_issue_tree(subtask['key'], tree, depth + 1) # recursive call of the function
return tree
def get_issue_tree_recursive(self, issue_key, tree=[], depth=0):
"""
Return list that contains the tree of the issue, with all subtasks and inward linked issues.
(!) Function only returns child issues from the same jira instance or from instance to which api key has access to.
(!) User asssociated with API key must have access to the all child issues in order to get them.
:param jira issue_key:
:param tree: blank parameter used for recursion. Don't change it.
:param depth: blank parameter used for recursion. Don't change it.
:return: list of dictioanries, key is the parent issue key, value is the child/linked issue key
"""

# Check the recursion depth. In case of any bugs that would result in infinite recursion, this will prevent the function from crashing your app. Python default for REcursionError is 1000
if depth > 50:
raise Exception("Recursion depth exceeded")
issue = self.get_issue(issue_key)
issue_links = issue["fields"]["issuelinks"]
subtasks = issue["fields"]["subtasks"]
for issue_link in issue_links:
if issue_link.get("inwardIssue") is not None:
parent_issue_key = issue["key"]
if not [
x for x in tree if issue_link["inwardIssue"]["key"] in x.keys()
]: # condition to avoid infinite recursion
tree.append({parent_issue_key: issue_link["inwardIssue"]["key"]})
self.get_issue_tree(
issue_link["inwardIssue"]["key"], tree, depth + 1
) # recursive call of the function
for subtask in subtasks:
if subtask.get("key") is not None:
parent_issue_key = issue["key"]
if not [x for x in tree if subtask["key"] in x.keys()]: # condition to avoid infinite recursion
tree.append({parent_issue_key: subtask["key"]})
self.get_issue_tree(subtask["key"], tree, depth + 1) # recursive call of the function
return tree

def create_or_update_issue_remote_links(
self,
Expand Down
2 changes: 2 additions & 0 deletions docs/jira.rst
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ Manage issues
# Scrap regex matches from issue description and comments:
jira.scrap_regex_from_issue(issue_key, regex)
# Get tree representation of issue and its subtasks + inward issue links
jira.get_issue_tree(issue_key)
Epic Issues
-------------
Expand Down
66 changes: 66 additions & 0 deletions examples/jira/jira_get_issue_tree_recursive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from atlassian import Jira
import networkx as nx # for visualisation of the tree
import matplotlib.pyplot as plt # for visualisation of the tree

# use one of above objects depending on your instance type cloud or DC
jira_cloud = Jira(url="<url>", username="username", password="password")
jira_dc = Jira(url="url", token="<token>>")

"""
Return list that contains the tree of the issue, with all subtasks and inward linked issues.
be aware of following limitations:
(!) Function only returns child issues from the same jira instance or from instance to which api key has access to.
(!) User asssociated with API key must have access to the all child issues in order to get them.
"""
"""
Let's say we have a tree of issues:
INTEGRTEST-2 is the root issue and it has 1 subtask from project TEST - TEST1
and also two linked issues from project INTEGRTEST - INTEGRTEST-3 and INTEGRTEST-4.
INTEGRTEST-4 has a subtask INTEGRTEST-6
-------------- graph representation of the tree ----------------
INTEGRTEST-2
TEST-1
INTEGRTEST-3
INTEGRTEST-4
INTEGRTEST-6
----------------------------------------------------------------
"""
output = jira_cloud.get_issue_tree_recursive("INTEGRTEST-2")


# print(output) will return:
# [{'INTEGRTEST-2': 'TEST-1'}, {'INTEGRTEST-2': 'INTEGRTEST-3'}, {'INTEGRTEST-2': 'INTEGRTEST-4'}, {'INTEGRTEST-4': 'INTEGRTEST-6'}]
# now we can use this output to create a graph representation of the tree:
def print_tree(node, dict_list, level=0):
children = [value for dict_item in dict_list for key, value in dict_item.items() if key == node]
print(" " * level + node)
for child in children:
print_tree(child, dict_list, level + 1)


# or use this input to create a visualisation using networkx and matplotlib librarries or some js library like recharts or vis.js
def make_graph(dict_list):
# Create a new directed graph
G = nx.DiGraph()
# Add an edge to the graph for each key-value pair in each dictionary
for d in dict_list:
for key, value in d.items():
G.add_edge(key, value)

# Generate a layout for the nodes
pos = nx.spring_layout(G)

# Define a color map for the nodes
color_map = []
for node in G:
if node.startswith("CYBER"):
color_map.append("blue")
else:
color_map.append("red")

# Draw the graph
nx.draw(G, pos, node_color=color_map, with_labels=True, node_size=1500)

# Display the graph
plt.show()

0 comments on commit 8c2824b

Please sign in to comment.