Skip to content

Commit 517ad79

Browse files
authored
[JIRA] added new method get_issue_tree_recursive + docs + examples (#1352)
* added update4d jira.py with new method + docs +exampl --------- Co-authored-by: gkowalc <>
1 parent 0ca67c5 commit 517ad79

File tree

3 files changed

+104
-0
lines changed

3 files changed

+104
-0
lines changed

atlassian/jira.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1705,6 +1705,42 @@ def get_issue_remote_links(self, issue_key, global_id=None, internal_id=None):
17051705
url += "/" + internal_id
17061706
return self.get(url, params=params)
17071707

1708+
def get_issue_tree_recursive(self, issue_key, tree=[], depth=0):
1709+
"""
1710+
Returns list that contains the tree structure of the root issue, with all subtasks and inward linked issues.
1711+
(!) Function only returns child issues from the same jira instance or from instance to which api key has access to.
1712+
(!) User asssociated with API key must have access to the all child issues in order to get them.
1713+
:param jira issue_key:
1714+
:param tree: blank parameter used for recursion. Don't change it.
1715+
:param depth: blank parameter used for recursion. Don't change it.
1716+
:return: list of dictioanries, key is the parent issue key, value is the child/linked issue key
1717+
1718+
"""
1719+
1720+
# 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
1721+
if depth > 50:
1722+
raise Exception("Recursion depth exceeded")
1723+
issue = self.get_issue(issue_key)
1724+
issue_links = issue["fields"]["issuelinks"]
1725+
subtasks = issue["fields"]["subtasks"]
1726+
for issue_link in issue_links:
1727+
if issue_link.get("inwardIssue") is not None:
1728+
parent_issue_key = issue["key"]
1729+
if not [
1730+
x for x in tree if issue_link["inwardIssue"]["key"] in x.keys()
1731+
]: # condition to avoid infinite recursion
1732+
tree.append({parent_issue_key: issue_link["inwardIssue"]["key"]})
1733+
self.get_issue_tree(
1734+
issue_link["inwardIssue"]["key"], tree, depth + 1
1735+
) # recursive call of the function
1736+
for subtask in subtasks:
1737+
if subtask.get("key") is not None:
1738+
parent_issue_key = issue["key"]
1739+
if not [x for x in tree if subtask["key"] in x.keys()]: # condition to avoid infinite recursion
1740+
tree.append({parent_issue_key: subtask["key"]})
1741+
self.get_issue_tree(subtask["key"], tree, depth + 1) # recursive call of the function
1742+
return tree
1743+
17081744
def create_or_update_issue_remote_links(
17091745
self,
17101746
issue_key,

docs/jira.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,8 @@ Manage issues
366366
# Scrap regex matches from issue description and comments:
367367
jira.scrap_regex_from_issue(issue_key, regex)
368368
369+
# Get tree representation of issue and its subtasks + inward issue links
370+
jira.get_issue_tree(issue_key)
369371
370372
Epic Issues
371373
-------------
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from atlassian import Jira
2+
import networkx as nx # for visualisation of the tree
3+
import matplotlib.pyplot as plt # for visualisation of the tree
4+
5+
# use one of above objects depending on your instance type cloud or DC
6+
jira_cloud = Jira(url="<url>", username="username", password="password")
7+
jira_dc = Jira(url="url", token="<token>>")
8+
9+
"""
10+
11+
Return list that contains the tree of the issue, with all subtasks and inward linked issues.
12+
be aware of following limitations:
13+
(!) Function only returns child issues from the same jira instance or from instance to which api key has access to.
14+
(!) User asssociated with API key must have access to the all child issues in order to get them.
15+
"""
16+
"""
17+
Let's say we have a tree of issues:
18+
INTEGRTEST-2 is the root issue and it has 1 subtask from project TEST - TEST1
19+
and also two linked issues from project INTEGRTEST - INTEGRTEST-3 and INTEGRTEST-4.
20+
INTEGRTEST-4 has a subtask INTEGRTEST-6
21+
-------------- graph representation of the tree ----------------
22+
INTEGRTEST-2
23+
TEST-1
24+
INTEGRTEST-3
25+
INTEGRTEST-4
26+
INTEGRTEST-6
27+
----------------------------------------------------------------
28+
"""
29+
output = jira_cloud.get_issue_tree_recursive("INTEGRTEST-2")
30+
31+
32+
# print(output) will return:
33+
# [{'INTEGRTEST-2': 'TEST-1'}, {'INTEGRTEST-2': 'INTEGRTEST-3'}, {'INTEGRTEST-2': 'INTEGRTEST-4'}, {'INTEGRTEST-4': 'INTEGRTEST-6'}]
34+
# now we can use this output to create a graph representation of the tree:
35+
def print_tree(node, dict_list, level=0):
36+
children = [value for dict_item in dict_list for key, value in dict_item.items() if key == node]
37+
print(" " * level + node)
38+
for child in children:
39+
print_tree(child, dict_list, level + 1)
40+
41+
42+
# or use this input to create a visualisation using networkx and matplotlib librarries or some js library like recharts or vis.js
43+
def make_graph(dict_list):
44+
# Create a new directed graph
45+
G = nx.DiGraph()
46+
# Add an edge to the graph for each key-value pair in each dictionary
47+
for d in dict_list:
48+
for key, value in d.items():
49+
G.add_edge(key, value)
50+
51+
# Generate a layout for the nodes
52+
pos = nx.spring_layout(G)
53+
54+
# Define a color map for the nodes
55+
color_map = []
56+
for node in G:
57+
if node.startswith("CYBER"):
58+
color_map.append("blue")
59+
else:
60+
color_map.append("red")
61+
62+
# Draw the graph
63+
nx.draw(G, pos, node_color=color_map, with_labels=True, node_size=1500)
64+
65+
# Display the graph
66+
plt.show()

0 commit comments

Comments
 (0)