|
1 | 1 | """Module for Jira search operations.""" |
2 | 2 |
|
3 | 3 | import logging |
| 4 | +from typing import Any |
4 | 5 |
|
5 | 6 | import requests |
6 | 7 | from requests.exceptions import HTTPError |
@@ -86,55 +87,61 @@ def search_issues( |
86 | 87 | fields_param = fields |
87 | 88 |
|
88 | 89 | if self.config.is_cloud: |
89 | | - actual_total = -1 |
90 | | - try: |
91 | | - # Call 1: Get metadata (including total) using standard search API |
92 | | - metadata_params = {"jql": jql, "maxResults": 0} |
93 | | - metadata_response = self.jira.get( |
94 | | - self.jira.resource_url("search"), params=metadata_params |
95 | | - ) |
| 90 | + # Cloud: Use v3 API endpoint POST /rest/api/3/search/jql |
| 91 | + # The old v2 /rest/api/*/search endpoint is deprecated |
| 92 | + # See: https://developer.atlassian.com/changelog/#CHANGE-2046 |
| 93 | + |
| 94 | + # Build request body for v3 API |
| 95 | + fields_list = fields_param.split(",") if fields_param else ["id", "key"] |
| 96 | + request_body: dict[str, Any] = { |
| 97 | + "jql": jql, |
| 98 | + "maxResults": min(limit, 100), # v3 API max is 100 per request |
| 99 | + "fields": fields_list, |
| 100 | + } |
| 101 | + # Note: v3 API uses 'expand' as a comma-separated string, not an array |
| 102 | + if expand: |
| 103 | + request_body["expand"] = expand |
96 | 104 |
|
97 | | - if ( |
98 | | - isinstance(metadata_response, dict) |
99 | | - and "total" in metadata_response |
100 | | - ): |
101 | | - try: |
102 | | - actual_total = int(metadata_response["total"]) |
103 | | - except (ValueError, TypeError): |
104 | | - logger.warning( |
105 | | - f"Could not parse 'total' from metadata response for JQL: {jql}. Received: {metadata_response.get('total')}" |
106 | | - ) |
107 | | - else: |
108 | | - logger.warning( |
109 | | - f"Could not retrieve total count from metadata response for JQL: {jql}. Response type: {type(metadata_response)}" |
110 | | - ) |
111 | | - except Exception as meta_err: |
112 | | - logger.error( |
113 | | - f"Error fetching metadata for JQL '{jql}': {str(meta_err)}" |
114 | | - ) |
| 105 | + # Fetch issues using v3 API with nextPageToken pagination |
| 106 | + all_issues: list[dict[str, Any]] = [] |
| 107 | + next_page_token: str | None = None |
115 | 108 |
|
116 | | - # Call 2: Get the actual issues using the enhanced method |
117 | | - issues_response_list = self.jira.enhanced_jql_get_list_of_tickets( |
118 | | - jql, fields=fields_param, limit=limit, expand=expand |
119 | | - ) |
| 109 | + while len(all_issues) < limit: |
| 110 | + if next_page_token: |
| 111 | + request_body["nextPageToken"] = next_page_token |
120 | 112 |
|
121 | | - if not isinstance(issues_response_list, list): |
122 | | - msg = f"Unexpected return value type from `jira.enhanced_jql_get_list_of_tickets`: {type(issues_response_list)}" |
123 | | - logger.error(msg) |
124 | | - raise TypeError(msg) |
| 113 | + response = self.jira.post( |
| 114 | + "rest/api/3/search/jql", json=request_body |
| 115 | + ) |
125 | 116 |
|
126 | | - response_dict_for_model = { |
127 | | - "issues": issues_response_list, |
128 | | - "total": actual_total, |
| 117 | + if not isinstance(response, dict): |
| 118 | + msg = f"Unexpected response type from v3 search API: {type(response)}" |
| 119 | + logger.error(msg) |
| 120 | + raise TypeError(msg) |
| 121 | + |
| 122 | + issues = response.get("issues", []) |
| 123 | + all_issues.extend(issues) |
| 124 | + |
| 125 | + # Check for more pages |
| 126 | + next_page_token = response.get("nextPageToken") |
| 127 | + if not next_page_token: |
| 128 | + break |
| 129 | + |
| 130 | + # Build response dict for model |
| 131 | + # Note: v3 API doesn't provide total count, so we use -1 |
| 132 | + response_dict: dict[str, Any] = { |
| 133 | + "issues": all_issues[:limit], |
| 134 | + "total": -1, |
| 135 | + "startAt": 0, |
| 136 | + "maxResults": limit, |
129 | 137 | } |
130 | 138 |
|
131 | 139 | search_result = JiraSearchResult.from_api_response( |
132 | | - response_dict_for_model, |
| 140 | + response_dict, |
133 | 141 | base_url=self.config.url, |
134 | 142 | requested_fields=fields_param, |
135 | 143 | ) |
136 | 144 |
|
137 | | - # Return the full search result object |
138 | 145 | return search_result |
139 | 146 | else: |
140 | 147 | limit = min(limit, 50) |
|
0 commit comments