Skip to content

Commit 74b4151

Browse files
committed
[Jira] bulk_issue() support pagination atlassian-api#880
1 parent 0dfebab commit 74b4151

3 files changed

Lines changed: 99 additions & 11 deletions

File tree

atlassian/jira/jira_server.py

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1485,27 +1485,81 @@ def epic_issues(self, epic: str, fields: Union[str, list] = "*all", expand: Opti
14851485
params["expand"] = expand
14861486
return self.get(url, params=params)
14871487

1488-
def bulk_issue(self, issue_list: list, fields: Union[str, list] = "*all"):
1489-
"""
1490-
:param fields:
1491-
:param list issue_list:
1492-
:return:
1488+
def bulk_issue(
1489+
self,
1490+
issue_list: list,
1491+
fields: Union[str, list] = "*all",
1492+
start: int = 0,
1493+
limit: Optional[int] = None,
1494+
fetch_all: bool = True,
1495+
):
1496+
"""Return issues matching the supplied issue keys.
1497+
1498+
Jira applies a maximum page size to search results. By default this
1499+
method follows every page and returns all matching issues in the
1500+
response's ``issues`` list. Set ``fetch_all`` to ``False`` to retain
1501+
the single-page REST response.
1502+
1503+
:param issue_list: Issue keys to retrieve.
1504+
:param fields: Fields to return for each issue.
1505+
:param start: First Server/Data Center result offset.
1506+
:param limit: Requested page size; Jira may apply a lower system limit.
1507+
:param fetch_all: Whether to collect all available result pages.
1508+
:return: A tuple of the Jira search response and issue keys reported
1509+
as invalid by Jira.
14931510
"""
14941511
jira_issue_regex = re.compile(r"\w+-\d+")
1495-
missing_issues = list()
1496-
matched_issue_keys = list()
1512+
missing_issues = []
1513+
matched_issue_keys = []
14971514
for key in issue_list:
14981515
if re.match(jira_issue_regex, key):
14991516
matched_issue_keys.append(key)
1500-
jql = f"key in ({', '.join(set(matched_issue_keys))})"
1501-
query_result = self.jql(jql, fields=fields)
1517+
jql = f"key in ({', '.join(dict.fromkeys(matched_issue_keys))})"
1518+
if self.cloud:
1519+
if start:
1520+
raise ValueError("Jira Cloud does not support offset pagination; use enhanced_jql instead.")
1521+
query_result = self.enhanced_jql(jql, fields=fields, limit=limit)
1522+
else:
1523+
query_result = self.jql(jql, fields=fields, start=start, limit=limit)
1524+
15021525
if query_result and "errorMessages" in list(query_result.keys()):
15031526
for message in query_result["errorMessages"]:
15041527
for key in issue_list:
15051528
if key in message:
15061529
missing_issues.append(key)
1507-
issue_list.remove(key)
1508-
query_result, missing_issues = self.bulk_issue(issue_list, fields)
1530+
remaining_issues = [key for key in issue_list if key not in missing_issues]
1531+
if remaining_issues != issue_list:
1532+
query_result, nested_missing_issues = self.bulk_issue(
1533+
remaining_issues, fields, start=start, limit=limit, fetch_all=fetch_all
1534+
)
1535+
missing_issues.extend(nested_missing_issues)
1536+
return query_result, missing_issues
1537+
1538+
if not fetch_all or not query_result:
1539+
return query_result, missing_issues
1540+
1541+
issues = list(query_result.get("issues", []))
1542+
if self.cloud:
1543+
next_page_token = query_result.get("nextPageToken")
1544+
while not query_result.get("isLast", True) and next_page_token:
1545+
query_result = self.enhanced_jql(jql, fields=fields, nextPageToken=next_page_token, limit=limit)
1546+
issues.extend(query_result.get("issues", []))
1547+
next_page_token = query_result.get("nextPageToken")
1548+
else:
1549+
next_start = start + len(issues)
1550+
total = query_result.get("total")
1551+
while issues and (total is None or next_start < total):
1552+
page = self.jql(jql, fields=fields, start=next_start, limit=limit)
1553+
if not page:
1554+
break
1555+
page_issues = page.get("issues", [])
1556+
if not page_issues:
1557+
break
1558+
issues.extend(page_issues)
1559+
next_start += len(page_issues)
1560+
total = page.get("total", total)
1561+
1562+
query_result["issues"] = issues
15091563
return query_result, missing_issues
15101564

15111565
def issue_createmeta(self, project: str, expand: str = "projects.issuetypes.fields") -> T_resp_json:

docs/jira.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,22 @@ Cloud client has a separate implementation and authorization model.
6868
for workflow in rules["values"]:
6969
print(workflow)
7070
71+
Bulk issue retrieval
72+
--------------------
73+
74+
``bulk_issue`` follows Jira search pages by default, so long issue-key lists
75+
are not silently limited by the server's configured page size. Set
76+
``fetch_all=False`` to receive only the first REST response.
77+
78+
.. code-block:: python
79+
80+
result, invalid_keys = jira.bulk_issue(
81+
["DEMO-1", "DEMO-2", "DEMO-3"],
82+
fields=["summary", "status"],
83+
)
84+
for issue in result["issues"]:
85+
print(issue["key"])
86+
7187
Manage Permissions
7288
------------------
7389

tests/test_jira_server_workflows.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,21 @@ def test_get_workflow_transition_rule_configurations_uses_v2_route(self):
2828
"draft": False,
2929
},
3030
)
31+
32+
def test_bulk_issue_collects_server_pages(self):
33+
jira = JiraServer("https://jira.example.com")
34+
first_page = {"issues": [{"key": "DEMO-1"}], "startAt": 0, "maxResults": 1, "total": 2}
35+
second_page = {"issues": [{"key": "DEMO-2"}], "startAt": 1, "maxResults": 1, "total": 2}
36+
37+
with patch.object(jira, "jql", side_effect=[first_page, second_page]) as jql:
38+
result, missing_issues = jira.bulk_issue(["DEMO-1", "DEMO-2"], fields=["summary"], limit=1)
39+
40+
self.assertEqual([issue["key"] for issue in result["issues"]], ["DEMO-1", "DEMO-2"])
41+
self.assertEqual(missing_issues, [])
42+
self.assertEqual(
43+
jql.call_args_list,
44+
[
45+
(("key in (DEMO-1, DEMO-2)",), {"fields": ["summary"], "start": 0, "limit": 1}),
46+
(("key in (DEMO-1, DEMO-2)",), {"fields": ["summary"], "start": 1, "limit": 1}),
47+
],
48+
)

0 commit comments

Comments
 (0)