diff --git a/examples/jql/main.go b/examples/jql/main.go index 9cb05f54..01de7056 100644 --- a/examples/jql/main.go +++ b/examples/jql/main.go @@ -13,10 +13,11 @@ func main() { jql := "project = Mesos and type = Bug and Status NOT IN (Resolved)" fmt.Printf("Usecase: Running a JQL query '%s'\n", jql) - issues, resp, err := jiraClient.Issue.Search(jql, nil) + issues, resp, warningMessages, err := jiraClient.Issue.Search(jql, nil) if err != nil { panic(err) } + checkWarnings(warningMessages) outputResponse(issues, resp) fmt.Println("") @@ -25,18 +26,27 @@ func main() { // Running an empty JQL query to get all tickets jql = "" fmt.Printf("Usecase: Running an empty JQL query to get all tickets\n") - issues, resp, err = jiraClient.Issue.Search(jql, nil) + issues, resp, warningMessages, err = jiraClient.Issue.Search(jql, nil) if err != nil { panic(err) } + checkWarnings(warningMessages) outputResponse(issues, resp) } func outputResponse(issues []jira.Issue, resp *jira.Response) { - fmt.Printf("Call to %s\n", resp.Request.URL) fmt.Printf("Response Code: %d\n", resp.StatusCode) fmt.Println("==================================") for _, i := range issues { fmt.Printf("%s (%s/%s): %+v\n", i.Key, i.Fields.Type.Name, i.Fields.Priority.Name, i.Fields.Summary) } } + +func checkWarnings(warningMessages []jira.WarningMsg) { + if len(warningMessages) > 0 { + fmt.Printf("Warning messages in response:\n") + for i, warn := range warningMessages { + fmt.Printf("Warning %d: %s\n", i, warn) + } + } +} diff --git a/examples/pagination/main.go b/examples/pagination/main.go index 571e9e12..73ffd6a1 100644 --- a/examples/pagination/main.go +++ b/examples/pagination/main.go @@ -19,11 +19,13 @@ func GetAllIssues(client *jira.Client, searchString string) ([]jira.Issue, error StartAt: last, } - chunk, resp, err := client.Issue.Search(searchString, opt) + chunk, resp, warningMessages, err := client.Issue.Search(searchString, opt) if err != nil { return nil, err } + checkWarnings(warningMessages) + total := resp.Total if issues == nil { issues = make([]jira.Issue, 0, total) @@ -53,3 +55,12 @@ func main() { fmt.Println(issues) } + +func checkWarnings(warningMessages []jira.WarningMsg) { + if len(warningMessages) > 0 { + fmt.Printf("Warning messages in response:\n") + for i, warn := range warningMessages { + fmt.Printf("Warning %d: %s\n", i, warn) + } + } +} diff --git a/go.mod b/go.mod index c786ab2e..48680301 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,5 @@ require ( github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 github.com/pkg/errors v0.9.1 github.com/trivago/tgo v1.0.7 - golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d ) diff --git a/go.sum b/go.sum index 59340733..0b7efdce 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,7 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -11,15 +10,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM= github.com/trivago/tgo v1.0.7/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/issue.go b/issue.go index 3e898d70..5b6f106d 100644 --- a/issue.go +++ b/issue.go @@ -513,12 +513,15 @@ type SearchOptions struct { // searchResult is only a small wrapper around the Search (with JQL) method // to be able to parse the results type searchResult struct { - Issues []Issue `json:"issues" structs:"issues"` - StartAt int `json:"startAt" structs:"startAt"` - MaxResults int `json:"maxResults" structs:"maxResults"` - Total int `json:"total" structs:"total"` + Issues []Issue `json:"issues" structs:"issues"` + StartAt int `json:"startAt" structs:"startAt"` + MaxResults int `json:"maxResults" structs:"maxResults"` + Total int `json:"total" structs:"total"` + WarningMessages []WarningMsg `json:"warningMessages,omitempty" structs:"warningMessages,omitempty"` } +type WarningMsg string + // GetQueryOptions specifies the optional parameters for the Get Issue methods type GetQueryOptions struct { // Fields is the list of fields to return for the issue. By default, all fields are returned. @@ -1058,7 +1061,7 @@ func (s *IssueService) AddLink(issueLink *IssueLink) (*Response, error) { // SearchWithContext will search for tickets according to the jql // // Jira API docs: https://developer.atlassian.com/jiradev/jira-apis/jira-rest-apis/jira-rest-api-tutorials/jira-rest-api-example-query-issues -func (s *IssueService) SearchWithContext(ctx context.Context, jql string, options *SearchOptions) ([]Issue, *Response, error) { +func (s *IssueService) SearchWithContext(ctx context.Context, jql string, options *SearchOptions) ([]Issue, *Response, []WarningMsg, error) { u := url.URL{ Path: "rest/api/2/search", } @@ -1089,7 +1092,7 @@ func (s *IssueService) SearchWithContext(ctx context.Context, jql string, option req, err := s.client.NewRequestWithContext(ctx, "GET", u.String(), nil) if err != nil { - return []Issue{}, nil, err + return []Issue{}, nil, nil, err } v := new(searchResult) @@ -1097,11 +1100,12 @@ func (s *IssueService) SearchWithContext(ctx context.Context, jql string, option if err != nil { err = NewJiraError(resp, err) } - return v.Issues, resp, err + + return v.Issues, resp, v.WarningMessages, err } // Search wraps SearchWithContext using the background context. -func (s *IssueService) Search(jql string, options *SearchOptions) ([]Issue, *Response, error) { +func (s *IssueService) Search(jql string, options *SearchOptions) ([]Issue, *Response, []WarningMsg, error) { return s.SearchWithContext(context.Background(), jql, options) } @@ -1120,7 +1124,7 @@ func (s *IssueService) SearchPagesWithContext(ctx context.Context, jql string, o options.MaxResults = 50 } - issues, resp, err := s.SearchWithContext(ctx, jql, options) + issues, resp, _, err := s.SearchWithContext(ctx, jql, options) if err != nil { return err } @@ -1142,7 +1146,7 @@ func (s *IssueService) SearchPagesWithContext(ctx context.Context, jql string, o } options.StartAt += resp.MaxResults - issues, resp, err = s.SearchWithContext(ctx, jql, options) + issues, resp, _, err = s.SearchWithContext(ctx, jql, options) if err != nil { return err } diff --git a/issue_test.go b/issue_test.go index 6664478f..a3bb1019 100644 --- a/issue_test.go +++ b/issue_test.go @@ -627,7 +627,7 @@ func TestIssueService_Search(t *testing.T) { }) opt := &SearchOptions{StartAt: 1, MaxResults: 40, Expand: "foo"} - _, resp, err := testClient.Issue.Search("type = Bug and Status NOT IN (Resolved)", opt) + _, resp, _, err := testClient.Issue.Search("type = Bug and Status NOT IN (Resolved)", opt) if resp == nil { t.Errorf("Response given: %+v", resp) @@ -647,6 +647,31 @@ func TestIssueService_Search(t *testing.T) { } } +func TestIssueService_SearchWithWarnings(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/search?expand=foo&jql=worklogauthor+%3D+123+and+type+%3D+Bug+and+Status+NOT+IN+%28Resolved%29&maxResults=10&startAt=1") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, `{"expand": "schema,names","startAt": 1,"maxResults": 10,"total": 6,"issues": [],"warningMessages": ["The value '123' does not exist for the field 'worklogAuthor'."]}`) + }) + + opt := &SearchOptions{StartAt: 1, MaxResults: 10, Expand: "foo"} + _, resp, warningMessages, err := testClient.Issue.Search("worklogauthor = 123 and type = Bug and Status NOT IN (Resolved)", opt) + + if resp == nil { + t.Errorf("Response given: %+v", resp) + } + if err != nil { + t.Errorf("Error given: %s", err) + } + + if len(warningMessages) != 1 { + t.Errorf("Expected 1 warning message, %v given", len(warningMessages)) + } +} + func TestIssueService_SearchEmptyJQL(t *testing.T) { setup() defer teardown() @@ -658,7 +683,7 @@ func TestIssueService_SearchEmptyJQL(t *testing.T) { }) opt := &SearchOptions{StartAt: 1, MaxResults: 40, Expand: "foo"} - _, resp, err := testClient.Issue.Search("", opt) + _, resp, _, err := testClient.Issue.Search("", opt) if resp == nil { t.Errorf("Response given: %+v", resp) @@ -687,7 +712,7 @@ func TestIssueService_Search_WithoutPaging(t *testing.T) { w.WriteHeader(http.StatusOK) fmt.Fprint(w, `{"expand": "schema,names","startAt": 0,"maxResults": 50,"total": 6,"issues": [{"expand": "html","id": "10230","self": "http://kelpie9:8081/rest/api/2/issue/BULK-62","key": "BULK-62","fields": {"summary": "testing","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/5","id": "5","description": "The sub-task of the issue","iconUrl": "http://kelpie9:8081/images/icons/issue_subtask.gif","name": "Sub-task","subtask": true},"customfield_10071": null}},{"expand": "html","id": "10004","self": "http://kelpie9:8081/rest/api/2/issue/BULK-47","key": "BULK-47","fields": {"summary": "Cheese v1 2.0 issue","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/3","id": "3","description": "A task that needs to be done.","iconUrl": "http://kelpie9:8081/images/icons/task.gif","name": "Task","subtask": false}}}]}`) }) - _, resp, err := testClient.Issue.Search("something", nil) + _, resp, _, err := testClient.Issue.Search("something", nil) if resp == nil { t.Errorf("Response given: %+v", resp)