Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 39ec958f04 | |||
| 097d46886d |
+26
-8
@@ -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; ; {
|
||||
nextPageToken := ""
|
||||
for {
|
||||
reqBody := map[string]any{
|
||||
"jql": jql,
|
||||
"fields": fieldList,
|
||||
"maxResults": 100,
|
||||
}
|
||||
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"`
|
||||
Total int `json:"total"`
|
||||
NextPageToken string `json:"nextPageToken"`
|
||||
IsLast bool `json:"isLast"`
|
||||
}
|
||||
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 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
@@ -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) |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user