Skip to content

Commit 9337933

Browse files
committed
Fix #715: Add IssueService.SearchV2JQL() to enable search via JQL
1 parent 51c7813 commit 9337933

File tree

3 files changed

+159
-15
lines changed

3 files changed

+159
-15
lines changed

cloud/examples/jql/main.go

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,19 @@ import (
88
)
99

1010
func main() {
11-
jiraClient, _ := jira.NewClient("https://issues.apache.org/jira/", nil)
11+
tp := jira.BasicAuthTransport{
12+
Username: "<username>",
13+
APIToken: "<api-token>",
14+
}
15+
jiraClient, _ := jira.NewClient("https://go-jira-opensource.atlassian.net/", tp.Client())
1216

1317
// Running JQL query
14-
15-
jql := "project = Mesos and type = Bug and Status NOT IN (Resolved)"
18+
jql := "type = Bug and Status NOT IN (Resolved)"
1619
fmt.Printf("Usecase: Running a JQL query '%s'\n", jql)
17-
issues, resp, err := jiraClient.Issue.Search(context.Background(), jql, nil)
18-
if err != nil {
19-
panic(err)
20+
options := &jira.SearchOptionsV2{
21+
Fields: []string{"*all"},
2022
}
21-
outputResponse(issues, resp)
22-
23-
fmt.Println("")
24-
fmt.Println("")
25-
26-
// Running an empty JQL query to get all tickets
27-
jql = ""
28-
fmt.Printf("Usecase: Running an empty JQL query to get all tickets\n")
29-
issues, resp, err = jiraClient.Issue.Search(context.Background(), jql, nil)
23+
issues, resp, err := jiraClient.Issue.SearchV2JQL(context.Background(), jql, options)
3024
if err != nil {
3125
panic(err)
3226
}

cloud/issue.go

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

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

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

1164+
// SearchV2JQL will search for tickets according to the jql for Jira Cloud
1165+
//
1166+
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-search/#api-rest-api-2-search-jql-get
1167+
func (s *IssueService) SearchV2JQL(ctx context.Context, jql string, options *SearchOptionsV2) ([]Issue, *Response, error) {
1168+
u := url.URL{
1169+
Path: "rest/api/2/search/jql",
1170+
}
1171+
uv := url.Values{}
1172+
if jql != "" {
1173+
uv.Add("jql", jql)
1174+
}
1175+
1176+
// TODO Check this out if this works with addOptions as well
1177+
if options != nil {
1178+
if options.NextPageToken != "" {
1179+
uv.Add("nextPageToken", options.NextPageToken)
1180+
}
1181+
if options.MaxResults != 0 {
1182+
uv.Add("maxResults", strconv.Itoa(options.MaxResults))
1183+
}
1184+
if strings.Join(options.Fields, ",") != "" {
1185+
uv.Add("fields", strings.Join(options.Fields, ","))
1186+
}
1187+
if options.Expand != "" {
1188+
uv.Add("expand", options.Expand)
1189+
}
1190+
if len(options.Properties) > 5 {
1191+
return nil, nil, fmt.Errorf("Search option Properties accepts maximum five entries")
1192+
}
1193+
if strings.Join(options.Properties, ",") != "" {
1194+
uv.Add("properties", strings.Join(options.Properties, ","))
1195+
}
1196+
if options.FieldsByKeys {
1197+
uv.Add("fieldsByKeys", "true")
1198+
}
1199+
if options.FailFast {
1200+
uv.Add("failFast", "true")
1201+
}
1202+
if len(options.ReconcileIssues) > 50 {
1203+
return nil, nil, fmt.Errorf("Search option ReconcileIssue accepts maximum 50 entries")
1204+
}
1205+
if len(options.ReconcileIssues) > 0 {
1206+
// TODO Extract this
1207+
// Convert []int to []string for strings.Join
1208+
reconcileIssuesStr := make([]string, len(options.ReconcileIssues))
1209+
for i, v := range options.ReconcileIssues {
1210+
reconcileIssuesStr[i] = strconv.Itoa(v)
1211+
}
1212+
uv.Add("reconcileIssues", strings.Join(reconcileIssuesStr, ","))
1213+
}
1214+
}
1215+
1216+
u.RawQuery = uv.Encode()
1217+
1218+
req, err := s.client.NewRequest(ctx, http.MethodGet, u.String(), nil)
1219+
if err != nil {
1220+
return []Issue{}, nil, err
1221+
}
1222+
1223+
v := new(searchResultV2)
1224+
resp, err := s.client.Do(req, v)
1225+
if err != nil {
1226+
err = NewJiraError(resp, err)
1227+
}
1228+
1229+
return v.Issues, resp, err
1230+
}
1231+
10891232
// SearchPages will get issues from all pages in a search
10901233
//
10911234
// Jira API docs: https://developer.atlassian.com/jiradev/jira-apis/jira-rest-apis/jira-rest-api-tutorials/jira-rest-api-example-query-issues

cloud/jira.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,10 @@ type Response struct {
275275
StartAt int
276276
MaxResults int
277277
Total int
278+
279+
// *searchResultV2
280+
IsLast bool
281+
NextPageToken string
278282
}
279283

280284
func newResponse(r *http.Response, v interface{}) *Response {
@@ -291,6 +295,9 @@ func (r *Response) populatePageValues(v interface{}) {
291295
r.StartAt = value.StartAt
292296
r.MaxResults = value.MaxResults
293297
r.Total = value.Total
298+
case *searchResultV2:
299+
r.IsLast = value.IsLast
300+
r.NextPageToken = value.NextPageToken
294301
case *groupMembersResult:
295302
r.StartAt = value.StartAt
296303
r.MaxResults = value.MaxResults

0 commit comments

Comments
 (0)