Merge pull request 'fix(jira): migrate to /rest/api/3/search/jql (legacy /search removed)' (#1) from fix/jira-search-jql-migration into main

Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2026-07-15 15:37:39 +00:00
2 changed files with 29 additions and 11 deletions
+28 -10
View File
@@ -2,9 +2,9 @@ package sync
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
)
@@ -47,23 +47,41 @@ type jiraIssue struct {
} `json:"fields"`
}
// search runs a JQL query via POST /rest/api/3/search/jql — the replacement
// for the removed GET /rest/api/3/search endpoint (Atlassian CHANGE-2046).
// It uses cursor pagination (nextPageToken/isLast); the legacy startAt/total
// fields no longer exist.
func (j *jiraConnector) search(ctx context.Context, jql, fields string) ([]jiraIssue, error) {
fieldList := strings.Split(fields, ",")
u := j.baseURL + "/rest/api/3/search/jql"
var all []jiraIssue
for startAt := 0; ; {
var page struct {
Issues []jiraIssue `json:"issues"`
Total int `json:"total"`
nextPageToken := ""
for {
reqBody := map[string]any{
"jql": jql,
"fields": fieldList,
"maxResults": 100,
}
u := fmt.Sprintf("%s/rest/api/3/search?jql=%s&fields=%s&maxResults=100&startAt=%d",
j.baseURL, url.QueryEscape(jql), url.QueryEscape(fields), startAt)
if err := getJSON(ctx, j.Authorize, u, &page); err != nil {
if nextPageToken != "" {
reqBody["nextPageToken"] = nextPageToken
}
body, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("jira search: encode request: %w", err)
}
var page struct {
Issues []jiraIssue `json:"issues"`
NextPageToken string `json:"nextPageToken"`
IsLast bool `json:"isLast"`
}
if err := doJSON(ctx, j.Authorize, http.MethodPost, u, body, &page); err != nil {
return nil, fmt.Errorf("jira search: %w", err)
}
all = append(all, page.Issues...)
startAt += len(page.Issues)
if len(page.Issues) == 0 || startAt >= page.Total {
if page.IsLast || page.NextPageToken == "" || len(page.Issues) == 0 {
return all, nil
}
nextPageToken = page.NextPageToken
}
}
+1 -1
View File
@@ -441,7 +441,7 @@ account** in that system, updated since `lastSyncAt 5 min` (overlap window):
| System | Query mechanism |
|---|---|
| Jira Cloud | `GET /rest/api/3/search?jql=assignee="<email>" AND project=<key> AND updated >= "<ts>"` (Basic auth: email+API token) |
| Jira Cloud | `POST /rest/api/3/search/jql` with body `{jql:'assignee="<email>" AND project=<key> AND updated >= "<ts>"', fields, maxResults}`, cursor-paginated via `nextPageToken`/`isLast` (Basic auth: email+API token). The legacy `GET /rest/api/3/search` was removed by Atlassian — CHANGE-2046. |
| Azure DevOps | `POST …/_apis/wit/wiql?api-version=7.1` with WIQL `[System.AssignedTo] = '<email>' AND [System.TeamProject] = '<project>' AND [System.ChangedDate] >= '<ts>'`, then batch `GET workitems` (PAT auth) |
| YouTrack | `GET /api/issues?query=assignee: <login> project: <key> updated: <ts> .. *` (Bearer permanent token) |