Skip to content

Commit f22badc

Browse files
committed
Add SearchV3 to fix deprecation of JQL search and evaluate expression endpoints
1 parent 47d27a7 commit f22badc

File tree

2 files changed

+158
-2
lines changed

2 files changed

+158
-2
lines changed

issue.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,63 @@ type SearchOptions struct {
527527
ValidateQuery string `url:"validateQuery,omitempty"`
528528
}
529529

530+
// SearchOptionsV3 specifies the parameters for the Jira Cloud-specific
531+
// paramaters to List methods that support pagination
532+
//
533+
// Docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-search/#api-rest-api-3-search-jql-get
534+
type SearchOptionsV3 struct {
535+
// NextPageToken: The token for a page to fetch that is not the first page.
536+
// The first page has a nextPageToken of null.
537+
// Use the nextPageToken to fetch the next page of issues.
538+
// Note: The nextPageToken field is not included in the response for the last page,
539+
// indicating there is no next page.
540+
NextPageToken string `url:"nextPageToken,omitempty"`
541+
542+
// MaxResults: The maximum number of items to return per page.
543+
// To manage page size, API may return fewer items per page where a large number of fields or properties are requested.
544+
// The greatest number of items returned per page is achieved when requesting id or key only.
545+
// It returns max 5000 issues.
546+
// Default: 50
547+
MaxResults int `url:"maxResults,omitempty"`
548+
549+
// Fields: A list of fields to return for each issue
550+
551+
// Fields: A list of fields to return for each issue, use it to retrieve a subset of fields.
552+
// This parameter accepts a comma-separated list. Expand options include:
553+
//
554+
// `*all` Returns all fields.
555+
// `*navigable` Returns navigable fields.
556+
// `id` Returns only issue IDs.
557+
// Any issue field, prefixed with a minus to exclude.
558+
//
559+
// The default is id.
560+
//
561+
// Examples:
562+
//
563+
// `summary,comment` Returns only the summary and comments fields only.
564+
// `-description` Returns all navigable (default) fields except description.
565+
// `*all,-comment` Returns all fields except comments.
566+
//
567+
// Multiple `fields` parameters can be included in a request.
568+
//
569+
// Note: By default, this resource returns IDs only. This differs from GET issue where the default is all fields.
570+
Fields []string
571+
572+
// Expand: Use expand to include additional information about issues in the response.
573+
// TODO add proper docs, see https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-search/#api-rest-api-3-search-jql-get
574+
Expand string `url:"expand,omitempty"`
575+
// A list of up to 5 issue properties to include in the results
576+
Properties []string `url:"properties,omitempty"`
577+
// FieldsByKeys: Reference fields by their key (rather than ID).
578+
// The default is false.
579+
FieldsByKeys bool `url:"fieldsByKeys,omitempty"`
580+
// FailFast: Fail this request early if we can't retrieve all field data.
581+
// Default false.
582+
FailFast bool `url:"failFast,omitempty"`
583+
// ReconcileIssues: Strong consistency issue ids to be reconciled with search results. Accepts max 50 ids
584+
ReconcileIssues []int `url:"reconcileIssues,omitempty"`
585+
}
586+
530587
// searchResult is only a small wrapper around the Search (with JQL) method
531588
// to be able to parse the results
532589
type searchResult struct {
@@ -536,6 +593,24 @@ type searchResult struct {
536593
Total int `json:"total" structs:"total"`
537594
}
538595

596+
// searchResult is only a small wrapper around the Jira Cloud-specific SearchV3 (with JQL) method
597+
// to be able to parse the results
598+
type searchResultV3 struct {
599+
// IsLast: Indicates whether this is the last page of the paginated response.
600+
IsLast bool `json:"isLast" structs:"isLast"`
601+
// Issues: The list of issues found by the search or reconsiliation.
602+
Issues []Issue `json:"issues" structs:"issues"`
603+
604+
// TODO Missing
605+
// Field names object
606+
// Field schema object
607+
608+
// NextPageToken: Continuation token to fetch the next page.
609+
// If this result represents the last or the only page this token will be null.
610+
// This token will expire in 7 days.
611+
NextPageToken string `json:"nextPageToken" structs:"nextPageToken"`
612+
}
613+
539614
// GetQueryOptions specifies the optional parameters for the Get Issue methods
540615
type GetQueryOptions struct {
541616
// Fields is the list of fields to return for the issue. By default, all fields are returned.
@@ -1134,6 +1209,79 @@ func (s *IssueService) Search(jql string, options *SearchOptions) ([]Issue, *Res
11341209
return s.SearchWithContext(context.Background(), jql, options)
11351210
}
11361211

1212+
// SearchV3JQL will search for tickets according to the jql for Jira Cloud
1213+
//
1214+
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-search/#api-rest-api-3-search-jql-get
1215+
func (s *IssueService) SearchV3JQL(jql string, options *SearchOptionsV3) ([]Issue, *Response, error) {
1216+
return s.SearchV3JQLWithContext(context.Background(), jql, options)
1217+
}
1218+
1219+
func (s *IssueService) SearchV3JQLWithContext(ctx context.Context, jql string, options *SearchOptionsV3) ([]Issue, *Response, error) {
1220+
u := url.URL{
1221+
Path: "rest/api/3/search/jql",
1222+
}
1223+
uv := url.Values{}
1224+
if jql != "" {
1225+
uv.Add("jql", jql)
1226+
}
1227+
1228+
// TODO Check this out if this works with addOptions as well
1229+
if options != nil {
1230+
if options.NextPageToken != "" {
1231+
uv.Add("nextPageToken", options.NextPageToken)
1232+
}
1233+
if options.MaxResults != 0 {
1234+
uv.Add("maxResults", strconv.Itoa(options.MaxResults))
1235+
}
1236+
if strings.Join(options.Fields, ",") != "" {
1237+
uv.Add("fields", strings.Join(options.Fields, ","))
1238+
}
1239+
if options.Expand != "" {
1240+
uv.Add("expand", options.Expand)
1241+
}
1242+
if len(options.Properties) > 5 {
1243+
return nil, nil, fmt.Errorf("Search option Properties accepts maximum five entries")
1244+
}
1245+
if strings.Join(options.Properties, ",") != "" {
1246+
uv.Add("properties", strings.Join(options.Properties, ","))
1247+
}
1248+
if options.FieldsByKeys {
1249+
uv.Add("fieldsByKeys", "true")
1250+
}
1251+
if options.FailFast {
1252+
uv.Add("failFast", "true")
1253+
}
1254+
if len(options.ReconcileIssues) > 50 {
1255+
return nil, nil, fmt.Errorf("Search option ReconcileIssue accepts maximum 50 entries")
1256+
}
1257+
if len(options.ReconcileIssues) > 0 {
1258+
// TODO Extract this
1259+
// Convert []int to []string for strings.Join
1260+
reconcileIssuesStr := make([]string, len(options.ReconcileIssues))
1261+
for i, v := range options.ReconcileIssues {
1262+
reconcileIssuesStr[i] = strconv.Itoa(v)
1263+
}
1264+
uv.Add("reconcileIssues", strings.Join(reconcileIssuesStr, ","))
1265+
}
1266+
}
1267+
1268+
u.RawQuery = uv.Encode()
1269+
1270+
req, err := s.client.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
1271+
if err != nil {
1272+
return []Issue{}, nil, err
1273+
}
1274+
1275+
v := new(searchResultV3)
1276+
resp, err := s.client.Do(req, v)
1277+
if err != nil {
1278+
err = NewJiraError(resp, err)
1279+
}
1280+
1281+
return v.Issues, resp, err
1282+
}
1283+
1284+
11371285
// SearchPagesWithContext will get issues from all pages in a search
11381286
//
11391287
// Jira API docs: https://developer.atlassian.com/jiradev/jira-apis/jira-rest-apis/jira-rest-api-tutorials/jira-rest-api-example-query-issues

jira.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,10 @@ type Response struct {
327327
StartAt int
328328
MaxResults int
329329
Total int
330+
331+
// *searchResultV3
332+
IsLast bool
333+
NextPageToken string
330334
}
331335

332336
func newResponse(r *http.Response, v interface{}) *Response {
@@ -343,6 +347,9 @@ func (r *Response) populatePageValues(v interface{}) {
343347
r.StartAt = value.StartAt
344348
r.MaxResults = value.MaxResults
345349
r.Total = value.Total
350+
case *searchResultV3:
351+
r.IsLast = value.IsLast
352+
r.NextPageToken = value.NextPageToken
346353
case *groupMembersResult:
347354
r.StartAt = value.StartAt
348355
r.MaxResults = value.MaxResults
@@ -561,8 +568,9 @@ func (t *CookieAuthTransport) transport() http.RoundTripper {
561568
//
562569
// Jira docs: https://developer.atlassian.com/cloud/jira/platform/understanding-jwt
563570
// Examples in other languages:
564-
// https://bitbucket.org/atlassian/atlassian-jwt-ruby/src/d44a8e7a4649e4f23edaa784402655fda7c816ea/lib/atlassian/jwt.rb
565-
// https://bitbucket.org/atlassian/atlassian-jwt-py/src/master/atlassian_jwt/url_utils.py
571+
//
572+
// https://bitbucket.org/atlassian/atlassian-jwt-ruby/src/d44a8e7a4649e4f23edaa784402655fda7c816ea/lib/atlassian/jwt.rb
573+
// https://bitbucket.org/atlassian/atlassian-jwt-py/src/master/atlassian_jwt/url_utils.py
566574
type JWTAuthTransport struct {
567575
Secret []byte
568576
Issuer string

0 commit comments

Comments
 (0)