Major JQL bugfixes

This commit is contained in:
2026-09-01 16:30:54 -05:00
parent bf5566e911
commit 6655b02f4a
4 changed files with 109 additions and 8 deletions
+43 -7
View File
@@ -21,8 +21,12 @@ from app.queries import get_open_ticket_numbers
from app.services.base import OrderService, NormalizedOrder
from app.status_rules import get_active_statuses
SEARCH_PAGE_SIZE = 50
SEARCH_PAGE_SIZE = 100
REQUEST_TIMEOUT_SECONDS = 30
# Hard ceiling on pages per fetch, purely as a backstop against the new
# search endpoint's documented pagination flakiness (tokens that repeat
# or never advance) - normal usage should never come close to this.
MAX_SEARCH_PAGES = 200
# Deliverable values come back like "SH011: Shipping - Return Label iPad -
# Physical in Box" - the part before the colon is the actual SKU code;
@@ -138,7 +142,23 @@ class JiraService(OrderService):
return combined_where + order_by_clause
def _search_all_issues(self, jql: str) -> List[dict]:
url = f"{self.base_url}/rest/api/3/search"
# /rest/api/3/search (with startAt/total pagination) has been
# deprecated and removed by Atlassian - /rest/api/3/search/jql is
# the replacement, using nextPageToken instead. That migration is
# NOT optional: hitting the old endpoint either errors outright or,
# worse, silently stops returning `total`, which made the old
# startAt-based loop here think page 1 was the whole result set -
# exactly the "only pulled tickets from this afternoon onward" bug,
# since with newest-first sorting, page 1 is just the most recent
# slice.
#
# The new endpoint's token pagination has its own documented
# flakiness in the wild (tokens that repeat or never advance), so
# this loop defends against that directly: it stops on an empty
# batch, an explicit isLast, a missing token, OR a token identical
# to one already seen - plus a hard page-count ceiling as a last
# resort so a pagination bug can never turn into an infinite loop.
url = f"{self.base_url}/rest/api/3/search/jql"
auth = (self.email, self.api_token)
headers = {"Accept": "application/json"}
@@ -152,15 +172,19 @@ class JiraService(OrderService):
fields += "," + ",".join(contact_field_ids)
all_issues: List[dict] = []
start_at = 0
next_page_token: str | None = None
seen_tokens: set[str] = set()
page_count = 0
while True:
params = {
"jql": jql,
"startAt": start_at,
"maxResults": SEARCH_PAGE_SIZE,
"fields": fields,
}
if next_page_token:
params["nextPageToken"] = next_page_token
try:
response = requests.get(
url,
@@ -193,12 +217,24 @@ class JiraService(OrderService):
batch = data.get("issues", [])
all_issues.extend(batch)
page_count += 1
total = data.get("total", len(all_issues))
start_at += len(batch)
if start_at >= total or not batch:
new_token = data.get("nextPageToken")
is_last = bool(data.get("isLast")) or not new_token
if (
not batch
or is_last
or not new_token
or new_token == next_page_token
or new_token in seen_tokens
or page_count >= MAX_SEARCH_PAGES
):
break
seen_tokens.add(new_token)
next_page_token = new_token
return all_issues
def _raw_values_from_field(self, fields: dict, field_id: str) -> List[str]: